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}