001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * 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 */ 017package org.jivesoftware.smack.tcp; 018 019import org.jivesoftware.smack.ConnectionConfiguration; 020import org.jivesoftware.smack.ConnectionCreationListener; 021import org.jivesoftware.smack.ConnectionListener; 022import org.jivesoftware.smack.SASLAuthentication; 023import org.jivesoftware.smack.SmackConfiguration; 024import org.jivesoftware.smack.SmackException; 025import org.jivesoftware.smack.SmackException.AlreadyLoggedInException; 026import org.jivesoftware.smack.SmackException.NoResponseException; 027import org.jivesoftware.smack.SmackException.NotConnectedException; 028import org.jivesoftware.smack.SmackException.ConnectionException; 029import org.jivesoftware.smack.XMPPConnection; 030import org.jivesoftware.smack.XMPPException; 031import org.jivesoftware.smack.compression.XMPPInputOutputStream; 032import org.jivesoftware.smack.packet.Packet; 033import org.jivesoftware.smack.packet.Presence; 034import org.jivesoftware.smack.parsing.ParsingExceptionCallback; 035import org.jivesoftware.smack.util.StringUtils; 036import org.jivesoftware.smack.util.dns.HostAddress; 037 038import javax.net.ssl.KeyManager; 039import javax.net.ssl.KeyManagerFactory; 040import javax.net.ssl.SSLContext; 041import javax.net.ssl.SSLSocket; 042import javax.security.auth.callback.Callback; 043import javax.security.auth.callback.CallbackHandler; 044import javax.security.auth.callback.PasswordCallback; 045import javax.security.sasl.SaslException; 046 047import java.io.BufferedReader; 048import java.io.BufferedWriter; 049import java.io.ByteArrayInputStream; 050import java.io.FileInputStream; 051import java.io.IOException; 052import java.io.InputStream; 053import java.io.InputStreamReader; 054import java.io.OutputStream; 055import java.io.OutputStreamWriter; 056import java.io.Reader; 057import java.io.UnsupportedEncodingException; 058import java.io.Writer; 059import java.lang.reflect.Constructor; 060import java.net.Socket; 061import java.security.KeyStore; 062import java.security.Provider; 063import java.security.Security; 064import java.util.Collection; 065import java.util.Iterator; 066import java.util.LinkedList; 067import java.util.List; 068import java.util.Locale; 069import java.util.logging.Level; 070import java.util.logging.Logger; 071 072/** 073 * Creates a socket connection to a XMPP server. This is the default connection 074 * to a Jabber server and is specified in the XMPP Core (RFC 3920). 075 * 076 * @see XMPPConnection 077 * @author Matt Tucker 078 */ 079public class XMPPTCPConnection extends XMPPConnection { 080 081 private static final Logger LOGGER = Logger.getLogger(XMPPTCPConnection.class.getName()); 082 083 /** 084 * The socket which is used for this connection. 085 */ 086 Socket socket; 087 088 String connectionID = null; 089 private String user = null; 090 private boolean connected = false; 091 // socketClosed is used concurrent 092 // by XMPPTCPConnection, PacketReader, PacketWriter 093 private volatile boolean socketClosed = false; 094 095 private boolean anonymous = false; 096 private boolean usingTLS = false; 097 098 private ParsingExceptionCallback parsingExceptionCallback = SmackConfiguration.getDefaultParsingExceptionCallback(); 099 100 PacketWriter packetWriter; 101 PacketReader packetReader; 102 103 /** 104 * Collection of available stream compression methods offered by the server. 105 */ 106 private Collection<String> compressionMethods; 107 108 /** 109 * Set to true by packet writer if the server acknowledged the compression 110 */ 111 private boolean serverAckdCompression = false; 112 113 /** 114 * Lock for the wait()/notify() pattern for the compression negotiation 115 */ 116 private final Object compressionLock = new Object(); 117 118 /** 119 * Creates a new connection to the specified XMPP server. A DNS SRV lookup will be 120 * performed to determine the IP address and port corresponding to the 121 * service name; if that lookup fails, it's assumed that server resides at 122 * <tt>serviceName</tt> with the default port of 5222. Encrypted connections (TLS) 123 * will be used if available, stream compression is disabled, and standard SASL 124 * mechanisms will be used for authentication.<p> 125 * <p/> 126 * This is the simplest constructor for connecting to an XMPP server. Alternatively, 127 * you can get fine-grained control over connection settings using the 128 * {@link #XMPPTCPConnection(ConnectionConfiguration)} constructor.<p> 129 * <p/> 130 * Note that XMPPTCPConnection constructors do not establish a connection to the server 131 * and you must call {@link #connect()}.<p> 132 * <p/> 133 * The CallbackHandler will only be used if the connection requires the client provide 134 * an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback 135 * to prompt for a password to unlock the keystore containing the SSL certificate. 136 * 137 * @param serviceName the name of the XMPP server to connect to; e.g. <tt>example.com</tt>. 138 * @param callbackHandler the CallbackHandler used to prompt for the password to the keystore. 139 */ 140 public XMPPTCPConnection(String serviceName, CallbackHandler callbackHandler) { 141 // Create the configuration for this new connection 142 super(new ConnectionConfiguration(serviceName)); 143 config.setCallbackHandler(callbackHandler); 144 } 145 146 /** 147 * Creates a new XMPP connection in the same way {@link #XMPPTCPConnection(String,CallbackHandler)} does, but 148 * with no callback handler for password prompting of the keystore. This will work 149 * in most cases, provided the client is not required to provide a certificate to 150 * the server. 151 * 152 * @param serviceName the name of the XMPP server to connect to; e.g. <tt>example.com</tt>. 153 */ 154 public XMPPTCPConnection(String serviceName) { 155 // Create the configuration for this new connection 156 super(new ConnectionConfiguration(serviceName)); 157 } 158 159 /** 160 * Creates a new XMPP connection in the same way {@link #XMPPTCPConnection(ConnectionConfiguration,CallbackHandler)} does, but 161 * with no callback handler for password prompting of the keystore. This will work 162 * in most cases, provided the client is not required to provide a certificate to 163 * the server. 164 * 165 * 166 * @param config the connection configuration. 167 */ 168 public XMPPTCPConnection(ConnectionConfiguration config) { 169 super(config); 170 } 171 172 /** 173 * Creates a new XMPP connection using the specified connection configuration.<p> 174 * <p/> 175 * Manually specifying connection configuration information is suitable for 176 * advanced users of the API. In many cases, using the 177 * {@link #XMPPTCPConnection(String)} constructor is a better approach.<p> 178 * <p/> 179 * Note that XMPPTCPConnection constructors do not establish a connection to the server 180 * and you must call {@link #connect()}.<p> 181 * <p/> 182 * 183 * The CallbackHandler will only be used if the connection requires the client provide 184 * an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback 185 * to prompt for a password to unlock the keystore containing the SSL certificate. 186 * 187 * @param config the connection configuration. 188 * @param callbackHandler the CallbackHandler used to prompt for the password to the keystore. 189 */ 190 public XMPPTCPConnection(ConnectionConfiguration config, CallbackHandler callbackHandler) { 191 super(config); 192 config.setCallbackHandler(callbackHandler); 193 } 194 195 public String getConnectionID() { 196 if (!isConnected()) { 197 return null; 198 } 199 return connectionID; 200 } 201 202 public String getUser() { 203 if (!isAuthenticated()) { 204 return null; 205 } 206 return user; 207 } 208 209 /** 210 * Install a parsing exception callback, which will be invoked once an exception is encountered while parsing a 211 * stanza 212 * 213 * @param callback the callback to install 214 */ 215 public void setParsingExceptionCallback(ParsingExceptionCallback callback) { 216 parsingExceptionCallback = callback; 217 } 218 219 /** 220 * Get the current active parsing exception callback. 221 * 222 * @return the active exception callback or null if there is none 223 */ 224 public ParsingExceptionCallback getParsingExceptionCallback() { 225 return parsingExceptionCallback; 226 } 227 228 @Override 229 public synchronized void login(String username, String password, String resource) throws XMPPException, SmackException, SaslException, IOException { 230 if (!isConnected()) { 231 throw new NotConnectedException(); 232 } 233 if (authenticated) { 234 throw new AlreadyLoggedInException(); 235 } 236 // Do partial version of nameprep on the username. 237 username = username.toLowerCase(Locale.US).trim(); 238 239 if (saslAuthentication.hasNonAnonymousAuthentication()) { 240 // Authenticate using SASL 241 if (password != null) { 242 saslAuthentication.authenticate(username, password, resource); 243 } 244 else { 245 saslAuthentication.authenticate(resource, config.getCallbackHandler()); 246 } 247 } else { 248 throw new SaslException("No non-anonymous SASL authentication mechanism available"); 249 } 250 251 // If compression is enabled then request the server to use stream compression. XEP-170 252 // recommends to perform stream compression before resource binding. 253 if (config.isCompressionEnabled()) { 254 useCompression(); 255 } 256 257 // Set the user. 258 String response = bindResourceAndEstablishSession(resource); 259 if (response != null) { 260 this.user = response; 261 // Update the serviceName with the one returned by the server 262 setServiceName(StringUtils.parseServer(response)); 263 } 264 else { 265 this.user = username + "@" + getServiceName(); 266 if (resource != null) { 267 this.user += "/" + resource; 268 } 269 } 270 271 // Indicate that we're now authenticated. 272 authenticated = true; 273 anonymous = false; 274 275 // Set presence to online. 276 if (config.isSendPresence()) { 277 sendPacket(new Presence(Presence.Type.available)); 278 } 279 280 // Stores the authentication for future reconnection 281 setLoginInfo(username, password, resource); 282 283 // If debugging is enabled, change the the debug window title to include the 284 // name we are now logged-in as. 285 // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger 286 // will be null 287 if (config.isDebuggerEnabled() && debugger != null) { 288 debugger.userHasLogged(user); 289 } 290 callConnectionAuthenticatedListener(); 291 } 292 293 @Override 294 public synchronized void loginAnonymously() throws XMPPException, SmackException, SaslException, IOException { 295 if (!isConnected()) { 296 throw new NotConnectedException(); 297 } 298 if (authenticated) { 299 throw new AlreadyLoggedInException(); 300 } 301 302 if (saslAuthentication.hasAnonymousAuthentication()) { 303 saslAuthentication.authenticateAnonymously(); 304 } 305 else { 306 throw new SaslException("No anonymous SASL authentication mechanism available"); 307 } 308 309 String response = bindResourceAndEstablishSession(null); 310 // Set the user value. 311 this.user = response; 312 // Update the serviceName with the one returned by the server 313 setServiceName(StringUtils.parseServer(response)); 314 315 // If compression is enabled then request the server to use stream compression 316 if (config.isCompressionEnabled()) { 317 useCompression(); 318 } 319 320 // Set presence to online. 321 sendPacket(new Presence(Presence.Type.available)); 322 323 // Indicate that we're now authenticated. 324 authenticated = true; 325 anonymous = true; 326 327 // If debugging is enabled, change the the debug window title to include the 328 // name we are now logged-in as. 329 // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger 330 // will be null 331 if (config.isDebuggerEnabled() && debugger != null) { 332 debugger.userHasLogged(user); 333 } 334 callConnectionAuthenticatedListener(); 335 } 336 337 public boolean isConnected() { 338 return connected; 339 } 340 341 public boolean isSecureConnection() { 342 return isUsingTLS(); 343 } 344 345 public boolean isSocketClosed() { 346 return socketClosed; 347 } 348 349 public boolean isAuthenticated() { 350 return authenticated; 351 } 352 353 public boolean isAnonymous() { 354 return anonymous; 355 } 356 357 /** 358 * Shuts the current connection down. After this method returns, the connection must be ready 359 * for re-use by connect. 360 */ 361 @Override 362 protected void shutdown() { 363 if (packetReader != null) { 364 packetReader.shutdown(); 365 } 366 if (packetWriter != null) { 367 packetWriter.shutdown(); 368 } 369 370 // Set socketClosed to true. This will cause the PacketReader 371 // and PacketWriter to ignore any Exceptions that are thrown 372 // because of a read/write from/to a closed stream. 373 // It is *important* that this is done before socket.close()! 374 socketClosed = true; 375 try { 376 socket.close(); 377 } catch (Exception e) { 378 LOGGER.log(Level.WARNING, "shutdown", e); 379 } 380 381 setWasAuthenticated(authenticated); 382 authenticated = false; 383 connected = false; 384 usingTLS = false; 385 reader = null; 386 writer = null; 387 } 388 389 @Override 390 protected void sendPacketInternal(Packet packet) throws NotConnectedException { 391 packetWriter.sendPacket(packet); 392 } 393 394 private void connectUsingConfiguration(ConnectionConfiguration config) throws SmackException, IOException { 395 Exception exception = null; 396 try { 397 maybeResolveDns(); 398 } 399 catch (Exception e) { 400 throw new SmackException(e); 401 } 402 Iterator<HostAddress> it = config.getHostAddresses().iterator(); 403 List<HostAddress> failedAddresses = new LinkedList<HostAddress>(); 404 while (it.hasNext()) { 405 exception = null; 406 HostAddress hostAddress = it.next(); 407 String host = hostAddress.getFQDN(); 408 int port = hostAddress.getPort(); 409 try { 410 if (config.getSocketFactory() == null) { 411 this.socket = new Socket(host, port); 412 } 413 else { 414 this.socket = config.getSocketFactory().createSocket(host, port); 415 } 416 } catch (Exception e) { 417 exception = e; 418 } 419 if (exception == null) { 420 // We found a host to connect to, break here 421 host = hostAddress.getFQDN(); 422 port = hostAddress.getPort(); 423 break; 424 } 425 hostAddress.setException(exception); 426 failedAddresses.add(hostAddress); 427 if (!it.hasNext()) { 428 // There are no more host addresses to try 429 // throw an exception and report all tried 430 // HostAddresses in the exception 431 throw new ConnectionException(failedAddresses); 432 } 433 } 434 socketClosed = false; 435 initConnection(); 436 } 437 438 /** 439 * Initializes the connection by creating a packet reader and writer and opening a 440 * XMPP stream to the server. 441 * 442 * @throws XMPPException if establishing a connection to the server fails. 443 * @throws SmackException if the server failes to respond back or if there is anther error. 444 * @throws IOException 445 */ 446 private void initConnection() throws SmackException, IOException { 447 boolean isFirstInitialization = packetReader == null || packetWriter == null; 448 compressionHandler = null; 449 serverAckdCompression = false; 450 451 // Set the reader and writer instance variables 452 initReaderAndWriter(); 453 454 try { 455 if (isFirstInitialization) { 456 packetWriter = new PacketWriter(this); 457 packetReader = new PacketReader(this); 458 459 // If debugging is enabled, we should start the thread that will listen for 460 // all packets and then log them. 461 if (config.isDebuggerEnabled()) { 462 addPacketListener(debugger.getReaderListener(), null); 463 if (debugger.getWriterListener() != null) { 464 addPacketSendingListener(debugger.getWriterListener(), null); 465 } 466 } 467 } 468 else { 469 packetWriter.init(); 470 packetReader.init(); 471 } 472 473 // Start the packet writer. This will open a XMPP stream to the server 474 packetWriter.startup(); 475 // Start the packet reader. The startup() method will block until we 476 // get an opening stream packet back from server. 477 packetReader.startup(); 478 479 // Make note of the fact that we're now connected. 480 connected = true; 481 482 if (isFirstInitialization) { 483 // Notify listeners that a new connection has been established 484 for (ConnectionCreationListener listener : getConnectionCreationListeners()) { 485 listener.connectionCreated(this); 486 } 487 } 488 489 } 490 catch (SmackException ex) { 491 // An exception occurred in setting up the connection. 492 shutdown(); 493 // Everything stoppped. Now throw the exception. 494 throw ex; 495 } 496 } 497 498 private void initReaderAndWriter() throws IOException { 499 try { 500 if (compressionHandler == null) { 501 reader = 502 new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); 503 writer = new BufferedWriter( 504 new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); 505 } 506 else { 507 try { 508 OutputStream os = compressionHandler.getOutputStream(socket.getOutputStream()); 509 writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); 510 511 InputStream is = compressionHandler.getInputStream(socket.getInputStream()); 512 reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); 513 } 514 catch (Exception e) { 515 LOGGER.log(Level.WARNING, "initReaderAndWriter()", e); 516 compressionHandler = null; 517 reader = new BufferedReader( 518 new InputStreamReader(socket.getInputStream(), "UTF-8")); 519 writer = new BufferedWriter( 520 new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); 521 } 522 } 523 } 524 catch (UnsupportedEncodingException ioe) { 525 throw new IllegalStateException(ioe); 526 } 527 528 // If debugging is enabled, we open a window and write out all network traffic. 529 initDebugger(); 530 } 531 532 /*********************************************** 533 * TLS code below 534 **********************************************/ 535 536 /** 537 * Returns true if the connection to the server has successfully negotiated TLS. Once TLS 538 * has been negotiatied the connection has been secured. 539 * 540 * @return true if the connection to the server has successfully negotiated TLS. 541 */ 542 public boolean isUsingTLS() { 543 return usingTLS; 544 } 545 546 /** 547 * Notification message saying that the server supports TLS so confirm the server that we 548 * want to secure the connection. 549 * 550 * @param required true when the server indicates that TLS is required. 551 * @throws IOException if an exception occurs. 552 */ 553 void startTLSReceived(boolean required) throws IOException { 554 if (required && config.getSecurityMode() == 555 ConnectionConfiguration.SecurityMode.disabled) { 556 notifyConnectionError(new IllegalStateException( 557 "TLS required by server but not allowed by connection configuration")); 558 return; 559 } 560 561 if (config.getSecurityMode() == ConnectionConfiguration.SecurityMode.disabled) { 562 // Do not secure the connection using TLS since TLS was disabled 563 return; 564 } 565 writer.write("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>"); 566 writer.flush(); 567 } 568 569 /** 570 * The server has indicated that TLS negotiation can start. We now need to secure the 571 * existing plain connection and perform a handshake. This method won't return until the 572 * connection has finished the handshake or an error occurred while securing the connection. 573 * 574 * @throws Exception if an exception occurs. 575 */ 576 void proceedTLSReceived() throws Exception { 577 SSLContext context = this.config.getCustomSSLContext(); 578 KeyStore ks = null; 579 KeyManager[] kms = null; 580 PasswordCallback pcb = null; 581 582 if(config.getCallbackHandler() == null) { 583 ks = null; 584 } else if (context == null) { 585 if(config.getKeystoreType().equals("NONE")) { 586 ks = null; 587 pcb = null; 588 } 589 else if(config.getKeystoreType().equals("PKCS11")) { 590 try { 591 Constructor<?> c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class); 592 String pkcs11Config = "name = SmartCard\nlibrary = "+config.getPKCS11Library(); 593 ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes()); 594 Provider p = (Provider)c.newInstance(config); 595 Security.addProvider(p); 596 ks = KeyStore.getInstance("PKCS11",p); 597 pcb = new PasswordCallback("PKCS11 Password: ",false); 598 this.config.getCallbackHandler().handle(new Callback[]{pcb}); 599 ks.load(null,pcb.getPassword()); 600 } 601 catch (Exception e) { 602 ks = null; 603 pcb = null; 604 } 605 } 606 else if(config.getKeystoreType().equals("Apple")) { 607 ks = KeyStore.getInstance("KeychainStore","Apple"); 608 ks.load(null,null); 609 //pcb = new PasswordCallback("Apple Keychain",false); 610 //pcb.setPassword(null); 611 } 612 else { 613 ks = KeyStore.getInstance(config.getKeystoreType()); 614 try { 615 pcb = new PasswordCallback("Keystore Password: ",false); 616 config.getCallbackHandler().handle(new Callback[]{pcb}); 617 ks.load(new FileInputStream(config.getKeystorePath()), pcb.getPassword()); 618 } 619 catch(Exception e) { 620 ks = null; 621 pcb = null; 622 } 623 } 624 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); 625 try { 626 if(pcb == null) { 627 kmf.init(ks,null); 628 } else { 629 kmf.init(ks,pcb.getPassword()); 630 pcb.clearPassword(); 631 } 632 kms = kmf.getKeyManagers(); 633 } catch (NullPointerException npe) { 634 kms = null; 635 } 636 } 637 638 // If the user didn't specify a SSLContext, use the default one 639 if (context == null) { 640 context = SSLContext.getInstance("TLS"); 641 context.init(kms, null, new java.security.SecureRandom()); 642 } 643 Socket plain = socket; 644 // Secure the plain connection 645 socket = context.getSocketFactory().createSocket(plain, 646 plain.getInetAddress().getHostAddress(), plain.getPort(), true); 647 // Initialize the reader and writer with the new secured version 648 initReaderAndWriter(); 649 650 try { 651 // Proceed to do the handshake 652 ((SSLSocket) socket).startHandshake(); 653 } 654 catch (IOException e) { 655 setConnectionException(e); 656 throw e; 657 } 658 //if (((SSLSocket) socket).getWantClientAuth()) { 659 // System.err.println("XMPPConnection wants client auth"); 660 //} 661 //else if (((SSLSocket) socket).getNeedClientAuth()) { 662 // System.err.println("XMPPConnection needs client auth"); 663 //} 664 //else { 665 // System.err.println("XMPPConnection does not require client auth"); 666 // } 667 // Set that TLS was successful 668 usingTLS = true; 669 670 // Set the new writer to use 671 packetWriter.setWriter(writer); 672 // Send a new opening stream to the server 673 packetWriter.openStream(); 674 } 675 676 /** 677 * Sets the available stream compression methods offered by the server. 678 * 679 * @param methods compression methods offered by the server. 680 */ 681 void setAvailableCompressionMethods(Collection<String> methods) { 682 compressionMethods = methods; 683 } 684 685 /** 686 * Returns the compression handler that can be used for one compression methods offered by the server. 687 * 688 * @return a instance of XMPPInputOutputStream or null if no suitable instance was found 689 * 690 */ 691 private XMPPInputOutputStream maybeGetCompressionHandler() { 692 if (compressionMethods != null) { 693 for (XMPPInputOutputStream handler : SmackConfiguration.getCompresionHandlers()) { 694 String method = handler.getCompressionMethod(); 695 if (compressionMethods.contains(method)) 696 return handler; 697 } 698 } 699 return null; 700 } 701 702 public boolean isUsingCompression() { 703 return compressionHandler != null && serverAckdCompression; 704 } 705 706 /** 707 * Starts using stream compression that will compress network traffic. Traffic can be 708 * reduced up to 90%. Therefore, stream compression is ideal when using a slow speed network 709 * connection. However, the server and the client will need to use more CPU time in order to 710 * un/compress network data so under high load the server performance might be affected. 711 * <p> 712 * <p> 713 * Stream compression has to have been previously offered by the server. Currently only the 714 * zlib method is supported by the client. Stream compression negotiation has to be done 715 * before authentication took place.<p> 716 * <p> 717 * 718 * @return true if stream compression negotiation was successful. 719 * @throws IOException if the compress stanza could not be send 720 */ 721 private boolean useCompression() throws IOException { 722 // If stream compression was offered by the server and we want to use 723 // compression then send compression request to the server 724 if (authenticated) { 725 throw new IllegalStateException("Compression should be negotiated before authentication."); 726 } 727 728 if ((compressionHandler = maybeGetCompressionHandler()) != null) { 729 synchronized (compressionLock) { 730 requestStreamCompression(compressionHandler.getCompressionMethod()); 731 // Wait until compression is being used or a timeout happened 732 try { 733 compressionLock.wait(getPacketReplyTimeout()); 734 } 735 catch (InterruptedException e) { 736 // Ignore. 737 } 738 } 739 return isUsingCompression(); 740 } 741 return false; 742 } 743 744 /** 745 * Request the server that we want to start using stream compression. When using TLS 746 * then negotiation of stream compression can only happen after TLS was negotiated. If TLS 747 * compression is being used the stream compression should not be used. 748 * @throws IOException if the compress stanza could not be send 749 */ 750 private void requestStreamCompression(String method) throws IOException { 751 writer.write("<compress xmlns='http://jabber.org/protocol/compress'>"); 752 writer.write("<method>" + method + "</method></compress>"); 753 writer.flush(); 754 } 755 756 /** 757 * Start using stream compression since the server has acknowledged stream compression. 758 * 759 * @throws IOException if there is an exception starting stream compression. 760 */ 761 void startStreamCompression() throws IOException { 762 serverAckdCompression = true; 763 // Initialize the reader and writer with the new secured version 764 initReaderAndWriter(); 765 766 // Set the new writer to use 767 packetWriter.setWriter(writer); 768 // Send a new opening stream to the server 769 packetWriter.openStream(); 770 // Notify that compression is being used 771 streamCompressionNegotiationDone(); 772 } 773 774 /** 775 * Notifies the XMPP connection that stream compression negotiation is done so that the 776 * connection process can proceed. 777 */ 778 void streamCompressionNegotiationDone() { 779 synchronized (compressionLock) { 780 compressionLock.notify(); 781 } 782 } 783 784 /** 785 * Establishes a connection to the XMPP server and performs an automatic login 786 * only if the previous connection state was logged (authenticated). It basically 787 * creates and maintains a socket connection to the server.<p> 788 * <p/> 789 * Listeners will be preserved from a previous connection if the reconnection 790 * occurs after an abrupt termination. 791 * 792 * @throws XMPPException if an error occurs while trying to establish the connection. 793 * @throws SmackException 794 * @throws IOException 795 */ 796 @Override 797 protected void connectInternal() throws SmackException, IOException, XMPPException { 798 // Establishes the connection, readers and writers 799 connectUsingConfiguration(config); 800 // TODO is there a case where connectUsing.. does not throw an exception but connected is 801 // still false? 802 if (connected) { 803 callConnectionConnectedListener(); 804 } 805 // Automatically makes the login if the user was previously connected successfully 806 // to the server and the connection was terminated abruptly 807 if (connected && wasAuthenticated) { 808 // Make the login 809 if (isAnonymous()) { 810 // Make the anonymous login 811 loginAnonymously(); 812 } 813 else { 814 login(config.getUsername(), config.getPassword(), config.getResource()); 815 } 816 notifyReconnection(); 817 } 818 } 819 820 /** 821 * Sends out a notification that there was an error with the connection 822 * and closes the connection. Also prints the stack trace of the given exception 823 * 824 * @param e the exception that causes the connection close event. 825 */ 826 synchronized void notifyConnectionError(Exception e) { 827 // Listeners were already notified of the exception, return right here. 828 if ((packetReader == null || packetReader.done) && 829 (packetWriter == null || packetWriter.done)) return; 830 831 // Closes the connection temporary. A reconnection is possible 832 shutdown(); 833 834 // Notify connection listeners of the error. 835 callConnectionClosedOnErrorListener(e); 836 } 837 838 @Override 839 protected void processPacket(Packet packet) { 840 super.processPacket(packet); 841 } 842 843 @Override 844 protected Reader getReader() { 845 return super.getReader(); 846 } 847 848 @Override 849 protected Writer getWriter() { 850 return super.getWriter(); 851 } 852 853 @Override 854 protected void throwConnectionExceptionOrNoResponse() throws IOException, NoResponseException { 855 super.throwConnectionExceptionOrNoResponse(); 856 } 857 858 @Override 859 protected void setServiceName(String serviceName) { 860 super.setServiceName(serviceName); 861 } 862 863 @Override 864 protected void serverRequiresBinding() { 865 super.serverRequiresBinding(); 866 } 867 868 @Override 869 protected void setServiceCapsNode(String node) { 870 super.setServiceCapsNode(node); 871 } 872 873 @Override 874 protected void serverSupportsSession() { 875 super.serverSupportsSession(); 876 } 877 878 @Override 879 protected void setRosterVersioningSupported() { 880 super.setRosterVersioningSupported(); 881 } 882 883 @Override 884 protected void serverSupportsAccountCreation() { 885 super.serverSupportsAccountCreation(); 886 } 887 888 @Override 889 protected SASLAuthentication getSASLAuthentication() { 890 return super.getSASLAuthentication(); 891 } 892 893 @Override 894 protected ConnectionConfiguration getConfiguration() { 895 return super.getConfiguration(); 896 } 897 898 /** 899 * Sends a notification indicating that the connection was reconnected successfully. 900 */ 901 private void notifyReconnection() { 902 // Notify connection listeners of the reconnection. 903 for (ConnectionListener listener : getConnectionListeners()) { 904 try { 905 listener.reconnectionSuccessful(); 906 } 907 catch (Exception e) { 908 // Catch and print any exception so we can recover 909 // from a faulty listener 910 LOGGER.log(Level.WARNING, "notifyReconnection()", e); 911 } 912 } 913 } 914}