001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.javamail.util; 019 020 import java.io.IOException; 021 import java.io.InputStream; 022 import java.io.OutputStream; 023 import java.io.PrintStream; 024 import java.lang.reflect.InvocationTargetException; 025 import java.lang.reflect.Method; 026 import java.net.InetAddress; 027 import java.net.Socket; 028 import java.net.UnknownHostException; 029 import java.util.ArrayList; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.StringTokenizer; 033 034 import javax.mail.MessagingException; 035 import javax.mail.Session; 036 import javax.net.ssl.SSLSocket; 037 038 import org.apache.geronimo.javamail.authentication.ClientAuthenticator; 039 import org.apache.geronimo.javamail.authentication.CramMD5Authenticator; 040 import org.apache.geronimo.javamail.authentication.DigestMD5Authenticator; 041 import org.apache.geronimo.javamail.authentication.LoginAuthenticator; 042 import org.apache.geronimo.javamail.authentication.PlainAuthenticator; 043 import org.apache.geronimo.javamail.authentication.SASLAuthenticator; 044 import org.apache.geronimo.javamail.util.CommandFailedException; 045 import org.apache.geronimo.javamail.util.InvalidCommandException; 046 047 /** 048 * Base class for all mail Store/Transport connection. Centralizes management 049 * of a lot of common connection handling. Actual protcol-specific 050 * functions are handled at the subclass level. 051 */ 052 public class MailConnection { 053 /** 054 * constants for EOL termination 055 */ 056 protected static final char CR = '\r'; 057 protected static final char LF = '\n'; 058 059 /** 060 * property keys for protocol properties. 061 */ 062 protected static final String MAIL_AUTH = "auth"; 063 protected static final String MAIL_PORT = "port"; 064 protected static final String MAIL_LOCALHOST = "localhost"; 065 protected static final String MAIL_STARTTLS_ENABLE = "starttls.enable"; 066 protected static final String MAIL_SSL_ENABLE = "ssl.enable"; 067 protected static final String MAIL_TIMEOUT = "timeout"; 068 protected static final String MAIL_SASL_ENABLE = "sasl.enable"; 069 protected static final String MAIL_SASL_REALM = "sasl.realm"; 070 protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid"; 071 protected static final String MAIL_SASL_MECHANISMS = "sasl.mechanisms"; 072 protected static final String MAIL_PLAIN_DISABLE = "auth.plain.disable"; 073 protected static final String MAIL_LOGIN_DISABLE = "auth.login.disable"; 074 protected static final String MAIL_FACTORY_CLASS = "socketFactory.class"; 075 protected static final String MAIL_FACTORY_FALLBACK = "socketFactory.fallback"; 076 protected static final String MAIL_FACTORY_PORT = "socketFactory.port"; 077 protected static final String MAIL_SSL_PROTOCOLS = "ssl.protocols"; 078 protected static final String MAIL_SSL_CIPHERSUITES = "ssl.ciphersuites"; 079 protected static final String MAIL_LOCALADDRESS = "localaddress"; 080 protected static final String MAIL_LOCALPORT = "localport"; 081 protected static final String MAIL_ENCODE_TRACE = "encodetrace"; 082 083 protected static final int MIN_MILLIS = 1000 * 60; 084 protected static final int TIMEOUT = MIN_MILLIS * 5; 085 protected static final String DEFAULT_MAIL_HOST = "localhost"; 086 087 protected static final String CAPABILITY_STARTTLS = "STARTTLS"; 088 089 protected static final String AUTHENTICATION_PLAIN = "PLAIN"; 090 protected static final String AUTHENTICATION_LOGIN = "LOGIN"; 091 protected static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5"; 092 protected static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5"; 093 094 // The mail Session we're associated with 095 protected Session session; 096 // The protocol we're implementing 097 protected String protocol; 098 // There are usually SSL and non-SSL versions of these protocols. This 099 // indicates which version we're using. 100 protected boolean sslConnection; 101 // This is the default port we should be using for making a connection. Each 102 // protocol (and each ssl version of the protocol) normally has a different default that 103 // should be used. 104 protected int defaultPort; 105 106 // a wrapper around our session to provide easier lookup of protocol 107 // specific property values 108 protected ProtocolProperties props; 109 110 // The target server host 111 protected String serverHost; 112 // The target server port 113 protected int serverPort; 114 115 // the connection socket...can be a plain socket or SSLSocket, if TLS is being used. 116 protected Socket socket; 117 118 // our local host name 119 protected InetAddress localAddress; 120 // our local port value 121 protected int localPort; 122 // our local host name 123 protected String localHost; 124 125 // our timeout value 126 protected int timeout; 127 128 // our login username 129 protected String username; 130 // our login password 131 protected String password; 132 // our SASL security realm 133 protected String realm; 134 // our authorization id 135 protected String authid; 136 137 // input stream used to read data. If Sasl is in use, this might be other than the 138 // direct access to the socket input stream. 139 protected InputStream inputStream; 140 // the other end of the connection pipeline. 141 protected OutputStream outputStream; 142 143 // our session provided debug output stream. 144 protected PrintStream debugStream; 145 // our debug flag (passed from the hosting transport) 146 protected boolean debug; 147 148 // list of authentication mechanisms supported by the server 149 protected List authentications; 150 // map of server extension arguments 151 protected Map capabilities; 152 // property list of authentication mechanisms 153 protected List mechanisms; 154 155 protected MailConnection(ProtocolProperties props) 156 { 157 // this is our properties retriever utility, which will look up 158 // properties based on the appropriate "mail.protocol." prefix. 159 // this also holds other information we might need for access, such as 160 // the protocol name and the Session; 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 // initialize our debug settings from the session 168 debug = session.getDebug(); 169 debugStream = session.getDebugOut(); 170 } 171 172 173 /** 174 * Connect to the server and do the initial handshaking. 175 * 176 * @param host The target host name. 177 * @param port The target port 178 * @param username The connection username (can be null) 179 * @param password The authentication password (can be null). 180 * 181 * @return true if we were able to obtain a connection and 182 * authenticate. 183 * @exception MessagingException 184 */ 185 public boolean protocolConnect(String host, int port, String username, String password) throws MessagingException { 186 // NOTE: We don't check for the username/password being null at this point. It's possible that 187 // the server will send back a PREAUTH response, which means we don't need to go through login 188 // processing. We'll need to check the capabilities response after we make the connection to decide 189 // if logging in is necesssary. 190 191 // save this for subsequent connections. All pool connections will use this info. 192 // if the port is defaulted, then see if we have something configured in the session. 193 // if not configured, we just use the default default. 194 if (port == -1) { 195 // check for a property and fall back on the default if it's not set. 196 port = props.getIntProperty(MAIL_PORT, props.getDefaultPort()); 197 // it's possible that -1 might have been explicitly set, so one last check. 198 if (port == -1) { 199 port = props.getDefaultPort(); 200 } 201 } 202 203 // Before we do anything, let's make sure that we succesfully received a host 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 // make sure we have the realm information 214 realm = props.getProperty(MAIL_SASL_REALM); 215 // get an authzid value, if we have one. The default is to use the username. 216 authid = props.getProperty(MAIL_AUTHORIZATIONID, username); 217 return true; 218 } 219 220 221 /** 222 * Establish a connection using an existing socket. 223 * 224 * @param s The socket to use. 225 */ 226 public void connect(Socket s) { 227 // just save the socket connection 228 this.socket = s; 229 } 230 231 232 /** 233 * Create a transport connection object and connect it to the 234 * target server. 235 * 236 * @exception MessagingException 237 */ 238 protected void getConnection() throws IOException, MessagingException 239 { 240 // We might have been passed a socket to connect with...if not, we need to create one of the correct type. 241 if (socket == null) { 242 // get the connection properties that control how we set this up. 243 getConnectionProperties(); 244 // if this is the SSL version of the protocol, we start with an SSLSocket 245 if (sslConnection) { 246 getConnectedSSLSocket(); 247 } 248 else 249 { 250 getConnectedSocket(); 251 } 252 } 253 // if we already have a socket, get some information from it and override what we've been passed. 254 else { 255 localPort = socket.getPort(); 256 localAddress = socket.getInetAddress(); 257 } 258 259 // now set up the input/output streams. 260 getConnectionStreams(); 261 } 262 263 /** 264 * Get common connection properties before creating a connection socket. 265 */ 266 protected void getConnectionProperties() { 267 268 // there are several protocol properties that can be set to tune the created socket. We need to 269 // retrieve those bits before creating the socket. 270 timeout = props.getIntProperty(MAIL_TIMEOUT, -1); 271 localAddress = null; 272 // see if we have a local address override. 273 String localAddrProp = props.getProperty(MAIL_LOCALADDRESS); 274 if (localAddrProp != null) { 275 try { 276 localAddress = InetAddress.getByName(localAddrProp); 277 } catch (UnknownHostException e) { 278 // not much we can do if this fails. 279 } 280 } 281 282 // check for a local port...default is to allow socket to choose. 283 localPort = props.getIntProperty(MAIL_LOCALPORT, 0); 284 } 285 286 287 /** 288 * Creates a connected socket 289 * 290 * @exception MessagingException 291 */ 292 protected void getConnectedSocket() throws IOException { 293 debugOut("Attempting plain socket connection to server " + serverHost + ":" + serverPort); 294 295 // check the properties that control how we connect. 296 getConnectionProperties(); 297 298 // the socket factory can be specified via a session property. By default, we just directly 299 // instantiate a socket without using a factory. 300 String socketFactory = props.getProperty(MAIL_FACTORY_CLASS); 301 302 // make sure the socket is nulled out to start 303 socket = null; 304 305 // if there is no socket factory defined (normal), we just create a socket directly. 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 // we choose the port used by the socket based on overrides. 315 Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort); 316 317 // use the current context loader to resolve this. 318 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 319 Class factoryClass = loader.loadClass(socketFactory); 320 321 // done indirectly, we need to invoke the method using reflection. 322 // This retrieves a factory instance. 323 Method getDefault = factoryClass.getMethod("getDefault", new Class[0]); 324 Object defFactory = getDefault.invoke(new Object(), new Object[0]); 325 326 // now that we have the factory, there are two different createSocket() calls we use, 327 // depending on whether we have a localAddress override. 328 329 if (localAddress != null) { 330 // retrieve the createSocket(String, int, InetAddress, int) method. 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 // retrieve the createSocket(String, int) method. 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 // if a socket factor is specified, then we may need to fall back to a default. This behavior 347 // is controlled by (surprise) more session properties. 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 // we have an exception. We're going to throw an IOException, which may require unwrapping 353 // or rewrapping the exception. 354 else { 355 // we have an exception from the reflection, so unwrap the base exception 356 if (e instanceof InvocationTargetException) { 357 e = ((InvocationTargetException)e).getTargetException(); 358 } 359 360 debugOut("Plain socket creation failure", e); 361 362 // throw this as an IOException, with the original exception attached. 363 IOException ioe = new IOException("Error connecting to " + serverHost + ", " + serverPort); 364 ioe.initCause(e); 365 throw ioe; 366 } 367 } 368 } 369 // if we have a timeout value, set that before returning 370 if (timeout >= 0) { 371 socket.setSoTimeout(timeout); 372 } 373 } 374 375 376 /** 377 * Creates a connected SSL socket for an initial SSL connection. 378 * 379 * @exception MessagingException 380 */ 381 protected void getConnectedSSLSocket() throws IOException { 382 debugOut("Attempting SSL socket connection to server " + serverHost + ":" + serverPort); 383 // the socket factory can be specified via a protocol property, a session property, and if all else 384 // fails (which it usually does), we fall back to the standard factory class. 385 String socketFactory = props.getProperty(MAIL_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory"); 386 387 // make sure this is null 388 socket = null; 389 390 // we'll try this with potentially two different factories if we're allowed to fall back. 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 // we choose the port used by the socket based on overrides. 400 Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort); 401 402 // use the current context loader to resolve this. 403 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 404 Class factoryClass = loader.loadClass(socketFactory); 405 406 // done indirectly, we need to invoke the method using reflection. 407 // This retrieves a factory instance. 408 Method getDefault = factoryClass.getMethod("getDefault", new Class[0]); 409 Object defFactory = getDefault.invoke(new Object(), new Object[0]); 410 411 // now that we have the factory, there are two different createSocket() calls we use, 412 // depending on whether we have a localAddress override. 413 414 if (localAddress != null) { 415 // retrieve the createSocket(String, int, InetAddress, int) method. 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 // retrieve the createSocket(String, int) method. 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 // if we're allowed to fallback, then use the default factory and try this again. We only 434 // allow this to happen once. 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 // we have an exception. We're going to throw an IOException, which may require unwrapping 442 // or rewrapping the exception. 443 else { 444 // we have an exception from the reflection, so unwrap the base exception 445 if (e instanceof InvocationTargetException) { 446 e = ((InvocationTargetException)e).getTargetException(); 447 } 448 449 debugOut("Failure creating SSL socket", e); 450 // throw this as an IOException, with the original exception attached. 451 IOException ioe = new IOException("Error connecting to " + serverHost + ", " + serverPort); 452 ioe.initCause(e); 453 throw ioe; 454 } 455 } 456 } 457 // and set the timeout value 458 if (timeout >= 0) { 459 socket.setSoTimeout(timeout); 460 } 461 462 // if there is a list of protocols specified, we need to break this down into 463 // the individual names 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 // and do the same for any cipher suites 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 * Switch the connection to using TLS level security, 493 * switching to an SSL socket. 494 */ 495 protected void getConnectedTLSSocket() throws MessagingException { 496 // it worked, now switch the socket into TLS mode 497 try { 498 499 // we use the same target and port as the current connection. 500 String host = socket.getInetAddress().getHostName(); 501 int port = socket.getPort(); 502 503 // the socket factory can be specified via a session property. By default, we use 504 // the native SSL factory. 505 String socketFactory = props.getProperty(MAIL_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory"); 506 507 // use the current context loader to resolve this. 508 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 509 Class factoryClass = loader.loadClass(socketFactory); 510 511 // done indirectly, we need to invoke the method using reflection. 512 // This retrieves a factory instance. 513 Method getDefault = factoryClass.getMethod("getDefault", new Class[0]); 514 Object defFactory = getDefault.invoke(new Object(), new Object[0]); 515 516 // now we need to invoke createSocket() 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 // and finally create the socket 523 Socket sslSocket = (Socket)createSocket.invoke(defFactory, createSocketArgs); 524 525 // if this is an instance of SSLSocket (very common), try setting the protocol to be 526 // "TLSv1". If this is some other class because of a factory override, we'll just have to 527 // accept that things will work. 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 // this is our active socket now 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 * Set up the input and output streams for server communications once the 550 * socket connection has been made. 551 * 552 * @exception MessagingException 553 */ 554 protected void getConnectionStreams() throws MessagingException, IOException { 555 // and finally, as a last step, replace our input streams with the secure ones. 556 // now set up the input/output streams. 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 * Close the server connection at termination. 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 * Verify that we have a good connection before 582 * attempting to send a command. 583 * 584 * @exception MessagingException 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 * Retrieve the SASL realm used for DIGEST-MD5 authentication. 595 * This will either be explicitly set, or retrieved using the 596 * mail.imap.sasl.realm session property. 597 * 598 * @return The current realm information (which can be null). 599 */ 600 public String getSASLRealm() { 601 // if the realm is null, retrieve it using the realm session property. 602 if (realm == null) { 603 realm = props.getProperty(MAIL_SASL_REALM); 604 } 605 return realm; 606 } 607 608 609 /** 610 * Explicitly set the SASL realm used for DIGEST-MD5 authenticaiton. 611 * 612 * @param name The new realm name. 613 */ 614 public void setSASLRealm(String name) { 615 realm = name; 616 } 617 618 619 /** 620 * Get a list of the SASL mechanisms we're configured to accept. 621 * 622 * @return A list of mechanisms we're allowed to use. 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 // the mechanisms are a blank or comma-separated list 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 * Get the list of authentication mechanisms the server 644 * is supposed to support. 645 * 646 * @return A list of the server supported authentication 647 * mechanisms. 648 */ 649 protected List getServerMechanisms() { 650 return authentications; 651 } 652 653 654 /** 655 * Merge the configured SASL mechanisms with the capabilities that the 656 * server has indicated it supports, returning a merged list that can 657 * be used for selecting a mechanism. 658 * 659 * @return A List representing the intersection of the configured list and the 660 * capabilities list. 661 */ 662 protected List selectSaslMechanisms() { 663 List configured = getSaslMechanisms(); 664 List supported = getServerMechanisms(); 665 666 // if not restricted, then we'll select from anything supported. 667 if (configured.isEmpty()) { 668 return supported; 669 } 670 671 List merged = new ArrayList(); 672 673 // we might need a subset of the supported ones 674 for (int i = 0; i < configured.size(); i++) { 675 // if this is in both lists, add to the merged one. 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 * Process SASL-type authentication. 687 * 688 * @return An authenticator to process the login challenge/response handling. 689 * @exception MessagingException 690 */ 691 protected ClientAuthenticator getLoginAuthenticator() throws MessagingException { 692 693 // get the list of mechanisms we're allowed to use. 694 List mechs = selectSaslMechanisms(); 695 696 try { 697 String[] mechArray = (String[])mechs.toArray(new String[0]); 698 // create a SASLAuthenticator, if we can. A failure likely indicates we're not 699 // running on a Java 5 VM, and the Sasl API isn't available. 700 return new SASLAuthenticator(mechArray, session.getProperties(), protocol, serverHost, getSASLRealm(), authid, username, password); 701 } catch (Throwable e) { 702 } 703 704 705 // now go through the progression of mechanisms we support, from the most secure to the 706 // least secure. 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 // can't find a mechanism we support in common 722 return null; 723 } 724 } 725 726 727 /** 728 * Internal debug output routine. 729 * 730 * @param value The string value to output. 731 */ 732 protected void debugOut(String message) { 733 if (debug) { 734 debugStream.println(protocol + " DEBUG: " + message); 735 } 736 } 737 738 /** 739 * Internal debugging routine for reporting exceptions. 740 * 741 * @param message A message associated with the exception context. 742 * @param e The received exception. 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 * Test if this connection has a given capability. 755 * 756 * @param capability The capability name. 757 * 758 * @return true if this capability is in the list, false for a mismatch. 759 */ 760 public boolean hasCapability(String capability) { 761 return capabilities.containsKey(capability); 762 } 763 764 /** 765 * Get the capabilities map. 766 * 767 * @return The capabilities map for the connection. 768 */ 769 public Map getCapabilities() { 770 return capabilities; 771 } 772 773 774 /** 775 * Test if the server supports a given mechanism. 776 * 777 * @param mech The mechanism name. 778 * 779 * @return true if the server has asserted support for the named 780 * mechanism. 781 */ 782 public boolean supportsMechanism(String mech) { 783 return authentications.contains(mech); 784 } 785 786 787 /** 788 * Retrieve the connection host. 789 * 790 * @return The host name. 791 */ 792 public String getHost() { 793 return serverHost; 794 } 795 796 797 /** 798 * Retrieve the local client host name. 799 * 800 * @return The string version of the local host name. 801 * @exception SMTPTransportException 802 */ 803 public String getLocalHost() throws MessagingException { 804 if (localHost == null) { 805 806 try { 807 localHost = InetAddress.getLocalHost().getHostName(); 808 } catch (UnknownHostException e) { 809 // fine, we're misconfigured - ignore 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 * Explicitly set the local host information. 832 * 833 * @param localHost 834 * The new localHost name. 835 */ 836 public void setLocalHost(String localHost) { 837 this.localHost = localHost; 838 } 839 }