1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.geronimo.javamail.util;
19
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.io.PrintStream;
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Method;
26 import java.net.InetAddress;
27 import java.net.Socket;
28 import java.net.UnknownHostException;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.StringTokenizer;
33
34 import javax.mail.MessagingException;
35 import javax.mail.Session;
36 import javax.net.ssl.SSLSocket;
37
38 import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
39 import org.apache.geronimo.javamail.authentication.CramMD5Authenticator;
40 import org.apache.geronimo.javamail.authentication.DigestMD5Authenticator;
41 import org.apache.geronimo.javamail.authentication.LoginAuthenticator;
42 import org.apache.geronimo.javamail.authentication.PlainAuthenticator;
43 import org.apache.geronimo.javamail.authentication.SASLAuthenticator;
44 import org.apache.geronimo.javamail.util.CommandFailedException;
45 import org.apache.geronimo.javamail.util.InvalidCommandException;
46
47
48
49
50
51
52 public class MailConnection {
53
54
55
56 protected static final char CR = '\r';
57 protected static final char LF = '\n';
58
59
60
61
62 protected static final String MAIL_AUTH = "auth";
63 protected static final String MAIL_PORT = "port";
64 protected static final String MAIL_LOCALHOST = "localhost";
65 protected static final String MAIL_STARTTLS_ENABLE = "starttls.enable";
66 protected static final String MAIL_SSL_ENABLE = "ssl.enable";
67 protected static final String MAIL_TIMEOUT = "timeout";
68 protected static final String MAIL_SASL_ENABLE = "sasl.enable";
69 protected static final String MAIL_SASL_REALM = "sasl.realm";
70 protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid";
71 protected static final String MAIL_SASL_MECHANISMS = "sasl.mechanisms";
72 protected static final String MAIL_PLAIN_DISABLE = "auth.plain.disable";
73 protected static final String MAIL_LOGIN_DISABLE = "auth.login.disable";
74 protected static final String MAIL_FACTORY_CLASS = "socketFactory.class";
75 protected static final String MAIL_FACTORY_FALLBACK = "socketFactory.fallback";
76 protected static final String MAIL_FACTORY_PORT = "socketFactory.port";
77 protected static final String MAIL_SSL_PROTOCOLS = "ssl.protocols";
78 protected static final String MAIL_SSL_CIPHERSUITES = "ssl.ciphersuites";
79 protected static final String MAIL_LOCALADDRESS = "localaddress";
80 protected static final String MAIL_LOCALPORT = "localport";
81 protected static final String MAIL_ENCODE_TRACE = "encodetrace";
82
83 protected static final int MIN_MILLIS = 1000 * 60;
84 protected static final int TIMEOUT = MIN_MILLIS * 5;
85 protected static final String DEFAULT_MAIL_HOST = "localhost";
86
87 protected static final String CAPABILITY_STARTTLS = "STARTTLS";
88
89 protected static final String AUTHENTICATION_PLAIN = "PLAIN";
90 protected static final String AUTHENTICATION_LOGIN = "LOGIN";
91 protected static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5";
92 protected static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5";
93
94
95 protected Session session;
96
97 protected String protocol;
98
99
100 protected boolean sslConnection;
101
102
103
104 protected int defaultPort;
105
106
107
108 protected ProtocolProperties props;
109
110
111 protected String serverHost;
112
113 protected int serverPort;
114
115
116 protected Socket socket;
117
118
119 protected InetAddress localAddress;
120
121 protected int localPort;
122
123 protected String localHost;
124
125
126 protected int timeout;
127
128
129 protected String username;
130
131 protected String password;
132
133 protected String realm;
134
135 protected String authid;
136
137
138
139 protected InputStream inputStream;
140
141 protected OutputStream outputStream;
142
143
144 protected PrintStream debugStream;
145
146 protected boolean debug;
147
148
149 protected List authentications;
150
151 protected Map capabilities;
152
153 protected List mechanisms;
154
155 protected MailConnection(ProtocolProperties props)
156 {
157
158
159
160
161 this.props = props;
162 this.protocol = props.getProtocol();
163 this.session = props.getSession();
164 this.sslConnection = props.getSSLConnection();
165 this.defaultPort = props.getDefaultPort();
166
167
168 debug = session.getDebug();
169 debugStream = session.getDebugOut();
170 }
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185 public boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
186
187
188
189
190
191
192
193
194 if (port == -1) {
195
196 port = props.getIntProperty(MAIL_PORT, props.getDefaultPort());
197
198 if (port == -1) {
199 port = props.getDefaultPort();
200 }
201 }
202
203
204 if ( host == null ) {
205 host = DEFAULT_MAIL_HOST;
206 }
207
208 this.serverHost = host;
209 this.serverPort = port;
210 this.username = username;
211 this.password = password;
212
213
214 realm = props.getProperty(MAIL_SASL_REALM);
215
216 authid = props.getProperty(MAIL_AUTHORIZATIONID, username);
217 return true;
218 }
219
220
221
222
223
224
225
226 public void connect(Socket s) {
227
228 this.socket = s;
229 }
230
231
232
233
234
235
236
237
238 protected void getConnection() throws IOException, MessagingException
239 {
240
241 if (socket == null) {
242
243 getConnectionProperties();
244
245 if (sslConnection) {
246 getConnectedSSLSocket();
247 }
248 else
249 {
250 getConnectedSocket();
251 }
252 }
253
254 else {
255 localPort = socket.getPort();
256 localAddress = socket.getInetAddress();
257 }
258
259
260 getConnectionStreams();
261 }
262
263
264
265
266 protected void getConnectionProperties() {
267
268
269
270 timeout = props.getIntProperty(MAIL_TIMEOUT, -1);
271 localAddress = null;
272
273 String localAddrProp = props.getProperty(MAIL_LOCALADDRESS);
274 if (localAddrProp != null) {
275 try {
276 localAddress = InetAddress.getByName(localAddrProp);
277 } catch (UnknownHostException e) {
278
279 }
280 }
281
282
283 localPort = props.getIntProperty(MAIL_LOCALPORT, 0);
284 }
285
286
287
288
289
290
291
292 protected void getConnectedSocket() throws IOException {
293 debugOut("Attempting plain socket connection to server " + serverHost + ":" + serverPort);
294
295
296 getConnectionProperties();
297
298
299
300 String socketFactory = props.getProperty(MAIL_FACTORY_CLASS);
301
302
303 socket = null;
304
305
306 if (socketFactory == null) {
307 socket = new Socket(serverHost, serverPort, localAddress, localPort);
308 }
309
310 else {
311 try {
312 int socketFactoryPort = props.getIntProperty(MAIL_FACTORY_PORT, -1);
313
314
315 Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort);
316
317
318 ClassLoader loader = Thread.currentThread().getContextClassLoader();
319 Class factoryClass = loader.loadClass(socketFactory);
320
321
322
323 Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
324 Object defFactory = getDefault.invoke(new Object(), new Object[0]);
325
326
327
328
329 if (localAddress != null) {
330
331 Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
332 Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
333
334 Object[] createSocketArgs = new Object[] { serverHost, portArg, localAddress, new Integer(localPort) };
335 socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
336 }
337 else {
338
339 Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
340 Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
341
342 Object[] createSocketArgs = new Object[] { serverHost, portArg };
343 socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
344 }
345 } catch (Throwable e) {
346
347
348 if (props.getBooleanProperty(MAIL_FACTORY_FALLBACK, false)) {
349 debugOut("First plain socket attempt failed, falling back to default factory", e);
350 socket = new Socket(serverHost, serverPort, localAddress, localPort);
351 }
352
353
354 else {
355
356 if (e instanceof InvocationTargetException) {
357 e = ((InvocationTargetException)e).getTargetException();
358 }
359
360 debugOut("Plain socket creation failure", e);
361
362
363 IOException ioe = new IOException("Error connecting to " + serverHost + ", " + serverPort);
364 ioe.initCause(e);
365 throw ioe;
366 }
367 }
368 }
369
370 if (timeout >= 0) {
371 socket.setSoTimeout(timeout);
372 }
373 }
374
375
376
377
378
379
380
381 protected void getConnectedSSLSocket() throws IOException {
382 debugOut("Attempting SSL socket connection to server " + serverHost + ":" + serverPort);
383
384
385 String socketFactory = props.getProperty(MAIL_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
386
387
388 socket = null;
389
390
391 boolean fallback = props.getBooleanProperty(MAIL_FACTORY_FALLBACK, false);
392
393 while (true) {
394 try {
395 debugOut("Creating SSL socket using factory " + socketFactory);
396
397 int socketFactoryPort = props.getIntProperty(MAIL_FACTORY_PORT, -1);
398
399
400 Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort);
401
402
403 ClassLoader loader = Thread.currentThread().getContextClassLoader();
404 Class factoryClass = loader.loadClass(socketFactory);
405
406
407
408 Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
409 Object defFactory = getDefault.invoke(new Object(), new Object[0]);
410
411
412
413
414 if (localAddress != null) {
415
416 Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
417 Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
418
419 Object[] createSocketArgs = new Object[] { serverHost, portArg, localAddress, new Integer(localPort) };
420 socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
421 break;
422 }
423 else {
424
425 Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
426 Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
427
428 Object[] createSocketArgs = new Object[] { serverHost, portArg };
429 socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
430 break;
431 }
432 } catch (Throwable e) {
433
434
435 if (fallback) {
436 debugOut("First attempt at creating SSL socket failed, falling back to default factory");
437 socketFactory = "javax.net.ssl.SSLSocketFactory";
438 fallback = false;
439 continue;
440 }
441
442
443 else {
444
445 if (e instanceof InvocationTargetException) {
446 e = ((InvocationTargetException)e).getTargetException();
447 }
448
449 debugOut("Failure creating SSL socket", e);
450
451 IOException ioe = new IOException("Error connecting to " + serverHost + ", " + serverPort);
452 ioe.initCause(e);
453 throw ioe;
454 }
455 }
456 }
457
458 if (timeout >= 0) {
459 socket.setSoTimeout(timeout);
460 }
461
462
463
464 String protocols = props.getProperty(MAIL_SSL_PROTOCOLS);
465 if (protocols != null) {
466 ArrayList<String> list = new ArrayList<String>();
467 StringTokenizer t = new StringTokenizer(protocols);
468
469 while (t.hasMoreTokens()) {
470 list.add(t.nextToken());
471 }
472
473 ((SSLSocket)socket).setEnabledProtocols(list.toArray(new String[list.size()]));
474 }
475
476
477 String suites = props.getProperty(MAIL_SSL_CIPHERSUITES);
478 if (suites != null) {
479 ArrayList<String> list = new ArrayList<String>();
480 StringTokenizer t = new StringTokenizer(suites);
481
482 while (t.hasMoreTokens()) {
483 list.add(t.nextToken());
484 }
485
486 ((SSLSocket)socket).setEnabledCipherSuites(list.toArray(new String[list.size()]));
487 }
488 }
489
490
491
492
493
494
495 protected void getConnectedTLSSocket() throws MessagingException {
496
497 try {
498
499
500 String host = socket.getInetAddress().getHostName();
501 int port = socket.getPort();
502
503
504
505 String socketFactory = props.getProperty(MAIL_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
506
507
508 ClassLoader loader = Thread.currentThread().getContextClassLoader();
509 Class factoryClass = loader.loadClass(socketFactory);
510
511
512
513 Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
514 Object defFactory = getDefault.invoke(new Object(), new Object[0]);
515
516
517 Class[] createSocketSig = new Class[] { Socket.class, String.class, Integer.TYPE, Boolean.TYPE };
518 Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
519
520 Object[] createSocketArgs = new Object[] { socket, host, new Integer(port), Boolean.TRUE };
521
522
523 Socket sslSocket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
524
525
526
527
528 if (sslSocket instanceof SSLSocket) {
529 ((SSLSocket)sslSocket).setEnabledProtocols(new String[] {"TLSv1"} );
530 ((SSLSocket)sslSocket).setUseClientMode(true);
531 debugOut("Initiating STARTTLS handshake");
532 ((SSLSocket)sslSocket).startHandshake();
533 }
534
535
536
537 socket = sslSocket;
538 getConnectionStreams();
539 debugOut("TLS connection established");
540 }
541 catch (Exception e) {
542 debugOut("Failure attempting to convert connection to TLS", e);
543 throw new MessagingException("Unable to convert connection to SSL", e);
544 }
545 }
546
547
548
549
550
551
552
553
554 protected void getConnectionStreams() throws MessagingException, IOException {
555
556
557 inputStream = new TraceInputStream(socket.getInputStream(), debugStream, debug, props.getBooleanProperty(
558 MAIL_ENCODE_TRACE, false));
559 outputStream = new TraceOutputStream(socket.getOutputStream(), debugStream, debug, props.getBooleanProperty(
560 MAIL_ENCODE_TRACE, false));
561 }
562
563
564
565
566
567 public void closeServerConnection()
568 {
569 try {
570 socket.close();
571 } catch (IOException ignored) {
572 }
573
574 socket = null;
575 inputStream = null;
576 outputStream = null;
577 }
578
579
580
581
582
583
584
585
586 protected void checkConnected() throws MessagingException {
587 if (socket == null || !socket.isConnected()) {
588 throw new MessagingException("no connection");
589 }
590 }
591
592
593
594
595
596
597
598
599
600 public String getSASLRealm() {
601
602 if (realm == null) {
603 realm = props.getProperty(MAIL_SASL_REALM);
604 }
605 return realm;
606 }
607
608
609
610
611
612
613
614 public void setSASLRealm(String name) {
615 realm = name;
616 }
617
618
619
620
621
622
623
624 protected List getSaslMechanisms() {
625 if (mechanisms == null) {
626 mechanisms = new ArrayList();
627 String mechList = props.getProperty(MAIL_SASL_MECHANISMS);
628 if (mechList != null) {
629
630 StringTokenizer tokenizer = new StringTokenizer(mechList, " ,");
631
632 while (tokenizer.hasMoreTokens()) {
633 String mech = tokenizer.nextToken().toUpperCase();
634 mechanisms.add(mech);
635 }
636 }
637 }
638 return mechanisms;
639 }
640
641
642
643
644
645
646
647
648
649 protected List getServerMechanisms() {
650 return authentications;
651 }
652
653
654
655
656
657
658
659
660
661
662 protected List selectSaslMechanisms() {
663 List configured = getSaslMechanisms();
664 List supported = getServerMechanisms();
665
666
667 if (configured.isEmpty()) {
668 return supported;
669 }
670
671 List merged = new ArrayList();
672
673
674 for (int i = 0; i < configured.size(); i++) {
675
676 String mech = (String)configured.get(i);
677 if (supported.contains(mech)) {
678 merged.add(mech);
679 }
680 }
681 return merged;
682 }
683
684
685
686
687
688
689
690
691 protected ClientAuthenticator getLoginAuthenticator() throws MessagingException {
692
693
694 List mechs = selectSaslMechanisms();
695
696 try {
697 String[] mechArray = (String[])mechs.toArray(new String[0]);
698
699
700 return new SASLAuthenticator(mechArray, session.getProperties(), protocol, serverHost, getSASLRealm(), authid, username, password);
701 } catch (Throwable e) {
702 }
703
704
705
706
707
708 if (mechs.contains(AUTHENTICATION_DIGESTMD5)) {
709 return new DigestMD5Authenticator(serverHost, username, password, getSASLRealm());
710 }
711 else if (mechs.contains(AUTHENTICATION_CRAMMD5)) {
712 return new CramMD5Authenticator(username, password);
713 }
714 else if (mechs.contains(AUTHENTICATION_LOGIN)) {
715 return new LoginAuthenticator(username, password);
716 }
717 else if (mechs.contains(AUTHENTICATION_PLAIN)) {
718 return new PlainAuthenticator(username, password);
719 }
720 else {
721
722 return null;
723 }
724 }
725
726
727
728
729
730
731
732 protected void debugOut(String message) {
733 if (debug) {
734 debugStream.println(protocol + " DEBUG: " + message);
735 }
736 }
737
738
739
740
741
742
743
744 protected void debugOut(String message, Throwable e) {
745 if (debug) {
746 debugOut("Received exception -> " + message);
747 debugOut("Exception message -> " + e.getMessage());
748 e.printStackTrace(debugStream);
749 }
750 }
751
752
753
754
755
756
757
758
759
760 public boolean hasCapability(String capability) {
761 return capabilities.containsKey(capability);
762 }
763
764
765
766
767
768
769 public Map getCapabilities() {
770 return capabilities;
771 }
772
773
774
775
776
777
778
779
780
781
782 public boolean supportsMechanism(String mech) {
783 return authentications.contains(mech);
784 }
785
786
787
788
789
790
791
792 public String getHost() {
793 return serverHost;
794 }
795
796
797
798
799
800
801
802
803 public String getLocalHost() throws MessagingException {
804 if (localHost == null) {
805
806 try {
807 localHost = InetAddress.getLocalHost().getHostName();
808 } catch (UnknownHostException e) {
809
810 }
811
812 if (localHost == null) {
813 localHost = props.getProperty(MAIL_LOCALHOST);
814 }
815
816 if (localHost == null) {
817 localHost = props.getSessionProperty(MAIL_LOCALHOST);
818 }
819
820 if (localHost == null) {
821 throw new MessagingException("Can't get local hostname. "
822 + " Please correctly configure JDK/DNS or set mail.smtp.localhost");
823 }
824 }
825
826 return localHost;
827 }
828
829
830
831
832
833
834
835
836 public void setLocalHost(String localHost) {
837 this.localHost = localHost;
838 }
839 }