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
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
200 protected String protocol;
201
202
203 protected String host;
204
205
206
207 protected int defaultPort;
208
209
210 protected int port;
211
212
213
214 protected Socket socket;
215
216
217 protected String localHost;
218
219
220
221
222 protected InputStream inputStream;
223
224
225 protected OutputStream outputStream;
226
227
228 protected HashMap serverAuthenticationMechanisms;
229
230
231 protected HashMap serverExtensionArgs;
232
233
234 protected boolean reportSuccess;
235
236
237 protected boolean serverTLS = false;
238
239
240 protected boolean useTLS = false;
241
242
243 protected boolean sslConnection = false;
244
245
246 protected String username;
247
248
249 protected String password;
250
251
252
253
254 protected String realm;
255
256
257 protected SMTPReply lastServerResponse = null;
258
259
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
300 this.defaultPort = defaultPort;
301 this.sslConnection = sslConnection;
302
303 reportSuccess = isProtocolPropertyTrue(MAIL_SMTP_REPORT_SUCCESS);
304
305 useTLS = isProtocolPropertyTrue(MAIL_SMTP_STARTTLS_ENABLE);
306
307
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
349
350
351
352 boolean mustAuthenticate = isProtocolPropertyTrue(MAIL_SMTP_AUTH);
353
354
355
356
357
358
359 if (mustAuthenticate && (username == null || password == null)) {
360 return false;
361 }
362
363
364
365
366 if (port == -1) {
367
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
378 getConnection(host, port, username, password);
379
380
381 if (!getWelcome()) {
382 throw new MessagingException("Error in getting welcome msg");
383 }
384
385
386 if (!sendHandshake()) {
387 throw new MessagingException("Error in saying EHLO to server");
388 }
389
390
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
421 if (message == null) {
422 throw new MessagingException("Null message");
423 }
424
425
426
427 if (!(message instanceof MimeMessage)) {
428 throw new MessagingException("SMTP can only send MimeMessages");
429 }
430
431
432 if (addresses == null || addresses.length == 0) {
433 throw new MessagingException("Null or empty address array");
434 }
435
436 boolean haveGroup = false;
437
438
439
440 for (int i = 0; i < addresses.length; i++) {
441 if (addresses[i] instanceof InternetAddress) {
442
443
444
445 if (((InternetAddress) addresses[i]).isGroup()) {
446 haveGroup = true;
447 }
448 } else {
449 throw new MessagingException("Illegal InternetAddress " + addresses[i]);
450 }
451 }
452
453
454 if (haveGroup) {
455 addresses = expandGroups(addresses);
456 }
457
458 SendStatus[] stats = new SendStatus[addresses.length];
459
460
461 Address[] sent = null;
462 Address[] unsent = null;
463 Address[] invalid = null;
464
465 try {
466
467
468
469 if (!sendMailFrom(message)) {
470 unsent = addresses;
471 sent = new Address[0];
472 invalid = new Address[0];
473
474 notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
475
476
477 SMTPReply last = lastServerResponse;
478
479 throw new SMTPSendFailedException("MAIL FROM", last.getCode(), last.getMessage(), null, sent, unsent,
480 invalid);
481 }
482
483 String dsn = null;
484
485
486
487
488
489
490 if (message instanceof SMTPMessage) {
491
492 int options = ((SMTPMessage) message).getNotifyOptions();
493
494 switch (options) {
495
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
516
517
518
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
538 if (dsn == null) {
539 dsn = getProtocolProperty(MAIL_SMTP_DSN_NOTIFY);
540 }
541
542
543
544
545 boolean sendFailure = false;
546
547
548
549
550
551
552
553
554
555
556 ArrayList sentAddresses = new ArrayList();
557 ArrayList unsentAddresses = new ArrayList();
558 ArrayList invalidAddresses = new ArrayList();
559
560
561
562 for (int i = 0; i < addresses.length; i++) {
563 InternetAddress target = (InternetAddress) addresses[i];
564
565
566 SendStatus status = sendRcptTo(target, dsn);
567 stats[i] = status;
568
569 switch (status.getStatus()) {
570
571 case SendStatus.SUCCESS:
572 sentAddresses.add(target);
573 break;
574
575
576
577
578 case SendStatus.INVALID_ADDRESS:
579 case SendStatus.GENERAL_ERROR:
580 sendFailure = true;
581 invalidAddresses.add(target);
582 break;
583
584
585 case SendStatus.SEND_FAILURE:
586 sendFailure = true;
587 unsentAddresses.add(target);
588 break;
589 }
590 }
591
592
593
594
595 if (sendFailure) {
596
597 boolean partialSends = false;
598
599
600 if (message instanceof SMTPMessage) {
601 partialSends = ((SMTPMessage) message).getSendPartial();
602 }
603
604
605
606 if (!partialSends) {
607 partialSends = isProtocolPropertyTrue(MAIL_SMTP_SENDPARTIAL);
608 }
609
610
611
612
613 if (!partialSends || sentAddresses.isEmpty()) {
614
615
616
617
618
619
620 unsentAddresses.addAll(sentAddresses);
621
622
623 sent = new Address[0];
624 unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
625 invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
626
627
628
629 resetConnection();
630
631
632 MessagingException failures = generateExceptionChain(stats, false);
633
634
635 throw new SMTPSendFailedException("MAIL TO", 0, "Invalid Address", failures, sent, unsent, invalid);
636 }
637 }
638
639 try {
640
641 sendData(message);
642 } catch (MessagingException e) {
643
644
645
646
647
648
649
650
651 unsentAddresses.addAll(sentAddresses);
652
653
654 sent = new Address[0];
655 unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
656 invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
657
658 notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
659
660 throw new SMTPSendFailedException("DATA", 0, "Send failure", e, sent, unsent, invalid);
661 }
662
663
664
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
670
671
672
673
674 if (sendFailure) {
675
676 notifyTransportListeners(TransportEvent.MESSAGE_PARTIALLY_DELIVERED, sent, unsent, invalid, message);
677
678
679
680
681 MessagingException failures = generateExceptionChain(stats, getReportSuccess());
682
683
684 throw new SMTPSendFailedException("MAIL TO", 0, "Invalid Address", failures, sent, unsent, invalid);
685 }
686
687
688 notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED, sent, unsent, invalid, message);
689
690
691
692
693 if (reportSuccess) {
694
695
696 MessagingException successes = generateExceptionChain(stats, reportSuccess);
697 if (successes != null) {
698 throw successes;
699 }
700 }
701 } catch (SMTPSendFailedException e) {
702
703
704 throw e;
705 } catch (MessagingException e) {
706
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
720 if (socket == null) {
721 return;
722 }
723 try {
724
725 sendQuit();
726 } finally {
727
728
729
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
754
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
775
776
777 SMTPReply last = lastServerResponse;
778
779
780 SMTPReply line = sendCommand("RSET");
781
782
783 if (line.getCode() != COMMAND_ACCEPTED) {
784 close();
785 }
786
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
804
805 for (int i = 0; i < addresses.length; i++) {
806 InternetAddress address = (InternetAddress) addresses[i];
807
808 if (!address.isGroup()) {
809 expandedAddresses.add(address);
810 } else {
811
812
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
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
840 useTLS = isProtocolPropertyTrue(MAIL_SMTP_TLS);
841 serverAuthenticationMechanisms = new HashMap();
842
843
844 if (socket == null) {
845
846 if (sslConnection) {
847 getConnectedSSLSocket();
848 } else {
849 getConnectedSocket();
850 }
851 }
852
853
854 else {
855 port = socket.getPort();
856 host = socket.getInetAddress().getHostName();
857 }
858
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
877
878
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
930
931
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
953 return Integer.parseInt(result);
954 } catch (NumberFormatException e) {
955 }
956 }
957
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
975
976
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
990
991
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
1035
1036
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
1066
1067
1068 String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS);
1069
1070
1071
1072
1073 int timeout = getIntProtocolProperty(MAIL_SMTP_TIMEOUT, -1);
1074 InetAddress localAddress = null;
1075
1076 String localAddrProp = getProtocolProperty(MAIL_SMTP_LOCALADDRESS);
1077 if (localAddrProp != null) {
1078 localAddress = InetAddress.getByName(localAddrProp);
1079 }
1080
1081
1082 int localPort = getIntProtocolProperty(MAIL_SMTP_LOCALPORT, 0);
1083
1084 socket = null;
1085
1086
1087
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
1097 Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
1098
1099
1100 ClassLoader loader = Thread.currentThread().getContextClassLoader();
1101 Class factoryClass = loader.loadClass(socketFactory);
1102
1103
1104
1105
1106 Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
1107 Object defFactory = getDefault.invoke(new Object(), new Object[0]);
1108
1109
1110
1111
1112
1113 if (localAddress != null) {
1114
1115
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
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
1131
1132
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
1140
1141
1142 else {
1143
1144
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
1154
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
1177
1178
1179
1180 String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS, getSessionProperty(MAIL_SSLFACTORY_CLASS,
1181 "javax.net.ssl.SSLSocketFactory"));
1182
1183
1184
1185
1186 int timeout = getIntProtocolProperty(MAIL_SMTP_TIMEOUT, -1);
1187 InetAddress localAddress = null;
1188
1189 String localAddrProp = getProtocolProperty(MAIL_SMTP_LOCALADDRESS);
1190 if (localAddrProp != null) {
1191 localAddress = InetAddress.getByName(localAddrProp);
1192 }
1193
1194
1195 int localPort = getIntProtocolProperty(MAIL_SMTP_LOCALPORT, 0);
1196
1197 socket = null;
1198
1199
1200
1201 if (socketFactory == null) {
1202 socket = new Socket(host, port, localAddress, localPort);
1203 }
1204
1205 else {
1206
1207
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
1219 Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
1220
1221
1222 ClassLoader loader = Thread.currentThread().getContextClassLoader();
1223 Class factoryClass = loader.loadClass(socketFactory);
1224
1225
1226
1227
1228 Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
1229 Object defFactory = getDefault.invoke(new Object(), new Object[0]);
1230
1231
1232
1233
1234
1235 if (localAddress != null) {
1236
1237
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
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
1254
1255
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
1265
1266
1267 else {
1268
1269
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
1279
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
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
1311 try {
1312
1313
1314 String host = socket.getInetAddress().getHostName();
1315 int port = socket.getPort();
1316
1317
1318
1319
1320 String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
1321
1322
1323 ClassLoader loader = Thread.currentThread().getContextClassLoader();
1324 Class factoryClass = loader.loadClass(socketFactory);
1325
1326
1327
1328 Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
1329 Object defFactory = getDefault.invoke(new Object(), new Object[0]);
1330
1331
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
1338 Socket sslSocket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
1339
1340
1341
1342
1343
1344
1345 if (sslSocket instanceof SSLSocket) {
1346 ((SSLSocket) sslSocket).setEnabledProtocols(new String[] { "TLSv1" });
1347 ((SSLSocket) sslSocket).setUseClientMode(true);
1348 ((SSLSocket) sslSocket).startHandshake();
1349 }
1350
1351
1352
1353
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
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
1386 SMTPReply line = sendCommand("DATA");
1387
1388 if (line.isError()) {
1389 throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
1390 }
1391
1392
1393 try {
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
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
1419 sendLine("");
1420 sendLine(".");
1421
1422
1423
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
1442
1443
1444
1445 if (isProtocolPropertyTrue(MAIL_SMTP_QUITWAIT)) {
1446 sendLine("QUIT");
1447 } else {
1448
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
1466
1467
1468
1469 StringBuffer command = new StringBuffer();
1470
1471
1472 command.append("RCPT TO: ");
1473 command.append(fixEmailAddress(addr.getAddress()));
1474
1475
1476 if (dsn != null) {
1477 command.append(" NOTIFY=");
1478 command.append(dsn);
1479 }
1480
1481
1482 String commandString = command.toString();
1483
1484 SMTPReply line = sendCommand(commandString);
1485
1486 switch (line.getCode()) {
1487
1488 case COMMAND_ACCEPTED:
1489 case ADDRESS_NOT_LOCAL:
1490
1491 return new SendStatus(SendStatus.SUCCESS, addr, commandString, line);
1492
1493
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
1500 return new SendStatus(SendStatus.INVALID_ADDRESS, addr, commandString, line);
1501
1502
1503 case SERVICE_NOT_AVAILABLE:
1504 case MAILBOX_BUSY:
1505 case PROCESSING_ERROR:
1506 case INSUFFICIENT_STORAGE:
1507 case MAILBOX_FULL:
1508
1509 return new SendStatus(SendStatus.SEND_FAILURE, addr, commandString, line);
1510
1511
1512 default:
1513
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
1529 String from = null;
1530
1531
1532
1533 if (message instanceof SMTPMessage) {
1534 from = ((SMTPMessage) message).getEnvelopeFrom();
1535 }
1536
1537
1538 if (from == null || from.length() == 0) {
1539
1540 from = getProtocolProperty(MAIL_SMTP_FROM);
1541 }
1542
1543
1544 if (from == null || from.length() == 0) {
1545 Address[] fromAddresses = message.getFrom();
1546
1547
1548
1549 if (fromAddresses != null && fromAddresses.length > 0) {
1550 from = ((InternetAddress) fromAddresses[0]).getAddress();
1551 }
1552
1553
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
1566 command.append("MAIL FROM: ");
1567 command.append(fixEmailAddress(from));
1568
1569
1570
1571 if (supportsExtension("DSN")) {
1572 String returnNotification = null;
1573
1574
1575
1576 if (message instanceof SMTPMessage) {
1577
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
1590
1591 if (returnNotification == null) {
1592
1593 returnNotification = getProtocolProperty(MAIL_SMTP_DSN_RET);
1594 }
1595
1596
1597
1598 if (returnNotification != null) {
1599 command.append(" RET=");
1600 command.append(returnNotification);
1601 }
1602 }
1603
1604
1605
1606
1607
1608 if (supportsExtension("AUTH")) {
1609 String submitter = null;
1610
1611
1612 if (message instanceof SMTPMessage) {
1613 submitter = ((SMTPMessage) message).getSubmitter();
1614 }
1615
1616 if (submitter == null) {
1617
1618 submitter = getProtocolProperty(MAIL_SMTP_SUBMITTER);
1619 }
1620
1621
1622 if (submitter != null) {
1623 command.append(" AUTH=");
1624 try {
1625
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
1636
1637
1638 if (message instanceof SMTPMessage) {
1639 extension = ((SMTPMessage) message).getMailExtension();
1640 }
1641
1642 if (extension == null) {
1643 extension = getProtocolProperty(MAIL_SMTP_EXTENSION);
1644 }
1645
1646
1647 if (extension != null && extension.length() != 0) {
1648
1649 command.append(' ');
1650 command.append(extension);
1651 }
1652
1653
1654 SMTPReply line = sendCommand(command.toString());
1655
1656
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
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
1758
1759
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
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
1810 boolean useEhlo = !isProtocolPropertyFalse(MAIL_SMTP_EHLO);
1811
1812
1813
1814 if (useEhlo) {
1815 if (!sendEhlo()) {
1816 sendHelo();
1817 }
1818 } else {
1819
1820 sendHelo();
1821 }
1822
1823 if (useTLS) {
1824
1825
1826 if (!serverTLS) {
1827 throw new MessagingException("Server doesn't support required transport level security");
1828 }
1829
1830
1831 getConnectedTLSSocket();
1832
1833
1834
1835
1836
1837
1838 serverAuthenticationMechanisms.clear();
1839 if (!sendEhlo()) {
1840 throw new MessagingException("Failure sending EHLO command to SMTP server");
1841 }
1842 }
1843
1844
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
1862
1863
1864
1865 if (line.getCode() != COMMAND_ACCEPTED) {
1866 return false;
1867 }
1868
1869
1870 serverExtensionArgs = new HashMap();
1871
1872
1873 while (line.isContinued()) {
1874
1875 line = getReply();
1876 if (line.getCode() != COMMAND_ACCEPTED) {
1877
1878 return false;
1879 }
1880
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
1897
1898
1899
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
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
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
2023
2024 if (delimiter != -1) {
2025 extensionName = extension.substring(0, delimiter).toUpperCase();
2026 argument = extension.substring(delimiter + 1);
2027 }
2028
2029
2030 serverExtensionArgs.put(extensionName, argument);
2031
2032
2033
2034 if (extensionName.equals("AUTH")) {
2035
2036 if (argument == null) {
2037 serverAuthenticationMechanisms.put("LOGIN", "LOGIN");
2038 } else {
2039
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
2049 else if (extensionName.equals("AUTH=LOGIN")) {
2050 serverAuthenticationMechanisms.put("LOGIN", "LOGIN");
2051 }
2052
2053 else if (extensionName.equals("STARTTLS")) {
2054
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
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
2113 if (!isProtocolPropertyTrue(MAIL_SMTP_AUTH)) {
2114 return true;
2115 }
2116
2117
2118
2119
2120 if (username == null || password == null) {
2121 return false;
2122 }
2123
2124 ClientAuthenticator authenticator = null;
2125
2126
2127
2128
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
2140 return false;
2141 }
2142
2143 if (debug) {
2144 debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
2145 }
2146
2147
2148
2149 if (authenticator.hasInitialResponse()) {
2150 StringBuffer command = new StringBuffer();
2151
2152 command.append("AUTH ");
2153
2154 command.append(authenticator.getMechanismName());
2155 command.append(" ");
2156
2157 command.append(new String(Base64.encode(authenticator.evaluateChallenge(null))));
2158
2159 sendLine(command.toString());
2160 }
2161
2162 else {
2163 StringBuffer command = new StringBuffer();
2164
2165 command.append("AUTH ");
2166
2167 command.append(authenticator.getMechanismName());
2168
2169 sendLine(command.toString());
2170 }
2171
2172
2173
2174
2175 while (true) {
2176
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
2187
2188 if (line.getCode() == AUTHENTICATION_COMPLETE) {
2189 if (debug) {
2190 debugOut("Successful SMTP authentication");
2191 }
2192 return true;
2193 }
2194
2195 else if (line.getCode() == AUTHENTICATION_CHALLENGE) {
2196
2197
2198
2199 if (authenticator.isComplete()) {
2200 return false;
2201 }
2202
2203
2204 byte[] challenge = Base64.decode(line.getMessage().getBytes());
2205
2206
2207
2208 sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge))));
2209 }
2210
2211
2212
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
2236 int status;
2237
2238
2239 InternetAddress address;
2240
2241
2242 String cmd;
2243
2244
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 }