001/**
002 *
003 * Copyright 2009 Jive Software,  2018 Florian Schmaus.
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;
018
019import java.io.ByteArrayInputStream;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.Reader;
024import java.io.Writer;
025import java.lang.reflect.Constructor;
026import java.security.KeyManagementException;
027import java.security.KeyStore;
028import java.security.KeyStoreException;
029import java.security.NoSuchAlgorithmException;
030import java.security.NoSuchProviderException;
031import java.security.Provider;
032import java.security.SecureRandom;
033import java.security.Security;
034import java.security.UnrecoverableKeyException;
035import java.security.cert.CertificateException;
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.HashMap;
039import java.util.LinkedHashMap;
040import java.util.LinkedList;
041import java.util.List;
042import java.util.Map;
043import java.util.Set;
044import java.util.concurrent.ConcurrentLinkedQueue;
045import java.util.concurrent.CopyOnWriteArraySet;
046import java.util.concurrent.Executor;
047import java.util.concurrent.ExecutorService;
048import java.util.concurrent.Executors;
049import java.util.concurrent.ThreadFactory;
050import java.util.concurrent.TimeUnit;
051import java.util.concurrent.atomic.AtomicInteger;
052import java.util.concurrent.locks.Lock;
053import java.util.concurrent.locks.ReentrantLock;
054import java.util.logging.Level;
055import java.util.logging.Logger;
056
057import javax.net.ssl.KeyManager;
058import javax.net.ssl.KeyManagerFactory;
059import javax.net.ssl.SSLContext;
060import javax.net.ssl.TrustManager;
061import javax.net.ssl.X509TrustManager;
062import javax.security.auth.callback.Callback;
063import javax.security.auth.callback.CallbackHandler;
064import javax.security.auth.callback.PasswordCallback;
065
066import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
067import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
068import org.jivesoftware.smack.SmackConfiguration.UnknownIqRequestReplyMode;
069import org.jivesoftware.smack.SmackException.AlreadyConnectedException;
070import org.jivesoftware.smack.SmackException.AlreadyLoggedInException;
071import org.jivesoftware.smack.SmackException.NoResponseException;
072import org.jivesoftware.smack.SmackException.NotConnectedException;
073import org.jivesoftware.smack.SmackException.NotLoggedInException;
074import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
075import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
076import org.jivesoftware.smack.SmackException.SecurityRequiredException;
077import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
078import org.jivesoftware.smack.XMPPException.FailedNonzaException;
079import org.jivesoftware.smack.XMPPException.StreamErrorException;
080import org.jivesoftware.smack.XMPPException.XMPPErrorException;
081import org.jivesoftware.smack.compress.packet.Compress;
082import org.jivesoftware.smack.compression.XMPPInputOutputStream;
083import org.jivesoftware.smack.debugger.SmackDebugger;
084import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
085import org.jivesoftware.smack.filter.IQReplyFilter;
086import org.jivesoftware.smack.filter.StanzaFilter;
087import org.jivesoftware.smack.filter.StanzaIdFilter;
088import org.jivesoftware.smack.iqrequest.IQRequestHandler;
089import org.jivesoftware.smack.packet.Bind;
090import org.jivesoftware.smack.packet.ErrorIQ;
091import org.jivesoftware.smack.packet.ExtensionElement;
092import org.jivesoftware.smack.packet.FullyQualifiedElement;
093import org.jivesoftware.smack.packet.IQ;
094import org.jivesoftware.smack.packet.Mechanisms;
095import org.jivesoftware.smack.packet.Message;
096import org.jivesoftware.smack.packet.Nonza;
097import org.jivesoftware.smack.packet.Presence;
098import org.jivesoftware.smack.packet.Session;
099import org.jivesoftware.smack.packet.Stanza;
100import org.jivesoftware.smack.packet.StanzaError;
101import org.jivesoftware.smack.packet.StartTls;
102import org.jivesoftware.smack.packet.StreamError;
103import org.jivesoftware.smack.packet.StreamOpen;
104import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
105import org.jivesoftware.smack.provider.ExtensionElementProvider;
106import org.jivesoftware.smack.provider.NonzaProvider;
107import org.jivesoftware.smack.provider.ProviderManager;
108import org.jivesoftware.smack.sasl.core.SASLAnonymous;
109import org.jivesoftware.smack.util.DNSUtil;
110import org.jivesoftware.smack.util.Objects;
111import org.jivesoftware.smack.util.PacketParserUtils;
112import org.jivesoftware.smack.util.ParserUtils;
113import org.jivesoftware.smack.util.StringUtils;
114import org.jivesoftware.smack.util.dns.HostAddress;
115import org.jivesoftware.smack.util.dns.SmackDaneProvider;
116import org.jivesoftware.smack.util.dns.SmackDaneVerifier;
117
118import org.jxmpp.jid.DomainBareJid;
119import org.jxmpp.jid.EntityFullJid;
120import org.jxmpp.jid.Jid;
121import org.jxmpp.jid.parts.Resourcepart;
122import org.jxmpp.util.XmppStringUtils;
123import org.minidns.dnsname.DnsName;
124import org.xmlpull.v1.XmlPullParser;
125
126
127/**
128 * This abstract class is commonly used as super class for XMPP connection mechanisms like TCP and BOSH. Hence it
129 * provides the methods for connection state management, like {@link #connect()}, {@link #login()} and
130 * {@link #disconnect()} (which are deliberately not provided by the {@link XMPPConnection} interface).
131 * <p>
132 * <b>Note:</b> The default entry point to Smack's documentation is {@link XMPPConnection}. If you are getting started
133 * with Smack, then head over to {@link XMPPConnection} and the come back here.
134 * </p>
135 * <h2>Parsing Exceptions</h2>
136 * <p>
137 * In case a Smack parser (Provider) throws those exceptions are handled over to the {@link ParsingExceptionCallback}. A
138 * common cause for a provider throwing is illegal input, for example a non-numeric String where only Integers are
139 * allowed. Smack's <em>default behavior</em> follows the <b>"fail-hard per default"</b> principle leading to a
140 * termination of the connection on parsing exceptions. This default was chosen to make users eventually aware that they
141 * should configure their own callback and handle those exceptions to prevent the disconnect. Handle a parsing exception
142 * could be as simple as using a non-throwing no-op callback, which would cause the faulty stream element to be taken
143 * out of the stream, i.e., Smack behaves like that element was never received.
144 * </p>
145 * <p>
146 * If the parsing exception is because Smack received illegal input, then please consider informing the authors of the
147 * originating entity about that. If it was thrown because of an bug in a Smack parser, then please consider filling a
148 * bug with Smack.
149 * </p>
150 * <h3>Managing the parsing exception callback</h3>
151 * <p>
152 * The "fail-hard per default" behavior is achieved by using the
153 * {@link org.jivesoftware.smack.parsing.ExceptionThrowingCallbackWithHint} as default parsing exception callback. You
154 * can change the behavior using {@link #setParsingExceptionCallback(ParsingExceptionCallback)} to set a new callback.
155 * Use {@link org.jivesoftware.smack.SmackConfiguration#setDefaultParsingExceptionCallback(ParsingExceptionCallback)} to
156 * set the default callback.
157 * </p>
158 */
159public abstract class AbstractXMPPConnection implements XMPPConnection {
160    private static final Logger LOGGER = Logger.getLogger(AbstractXMPPConnection.class.getName());
161
162    protected static final SmackReactor SMACK_REACTOR;
163
164    static {
165        SMACK_REACTOR = SmackReactor.getInstance();
166    }
167
168    /**
169     * Counter to uniquely identify connections that are created.
170     */
171    private static final AtomicInteger connectionCounter = new AtomicInteger(0);
172
173    static {
174        // Ensure the SmackConfiguration class is loaded by calling a method in it.
175        SmackConfiguration.getVersion();
176    }
177
178    /**
179     * A collection of ConnectionListeners which listen for connection closing
180     * and reconnection events.
181     */
182    protected final Set<ConnectionListener> connectionListeners =
183            new CopyOnWriteArraySet<>();
184
185    /**
186     * A collection of StanzaCollectors which collects packets for a specified filter
187     * and perform blocking and polling operations on the result queue.
188     * <p>
189     * We use a ConcurrentLinkedQueue here, because its Iterator is weakly
190     * consistent and we want {@link #invokeStanzaCollectorsAndNotifyRecvListeners(Stanza)} for-each
191     * loop to be lock free. As drawback, removing a StanzaCollector is O(n).
192     * The alternative would be a synchronized HashSet, but this would mean a
193     * synchronized block around every usage of <code>collectors</code>.
194     * </p>
195     */
196    private final Collection<StanzaCollector> collectors = new ConcurrentLinkedQueue<>();
197
198    /**
199     * List of PacketListeners that will be notified synchronously when a new stanza was received.
200     */
201    private final Map<StanzaListener, ListenerWrapper> syncRecvListeners = new LinkedHashMap<>();
202
203    /**
204     * List of PacketListeners that will be notified asynchronously when a new stanza was received.
205     */
206    private final Map<StanzaListener, ListenerWrapper> asyncRecvListeners = new LinkedHashMap<>();
207
208    /**
209     * List of PacketListeners that will be notified when a new stanza was sent.
210     */
211    private final Map<StanzaListener, ListenerWrapper> sendListeners =
212            new HashMap<>();
213
214    /**
215     * List of PacketListeners that will be notified when a new stanza is about to be
216     * sent to the server. These interceptors may modify the stanza before it is being
217     * actually sent to the server.
218     */
219    private final Map<StanzaListener, InterceptorWrapper> interceptors =
220            new HashMap<>();
221
222    final Map<String, NonzaCallback> nonzaCallbacks = new HashMap<>();
223
224    protected final Lock connectionLock = new ReentrantLock();
225
226    protected final Map<String, FullyQualifiedElement> streamFeatures = new HashMap<>();
227
228    /**
229     * The full JID of the authenticated user, as returned by the resource binding response of the server.
230     * <p>
231     * It is important that we don't infer the user from the login() arguments and the configurations service name, as,
232     * for example, when SASL External is used, the username is not given to login but taken from the 'external'
233     * certificate.
234     * </p>
235     */
236    protected EntityFullJid user;
237
238    protected boolean connected = false;
239
240    /**
241     * The stream ID, see RFC 6120 § 4.7.3
242     */
243    protected String streamId;
244
245    /**
246     * The timeout to wait for a reply in milliseconds.
247     */
248    private long replyTimeout = SmackConfiguration.getDefaultReplyTimeout();
249
250    /**
251     * The SmackDebugger allows to log and debug XML traffic.
252     */
253    protected final SmackDebugger debugger;
254
255    /**
256     * The Reader which is used for the debugger.
257     */
258    protected Reader reader;
259
260    /**
261     * The Writer which is used for the debugger.
262     */
263    protected Writer writer;
264
265    protected final SynchronizationPoint<SmackException> tlsHandled = new SynchronizationPoint<>(this, "establishing TLS");
266
267    /**
268     * Set to success if the last features stanza from the server has been parsed. A XMPP connection
269     * handshake can invoke multiple features stanzas, e.g. when TLS is activated a second feature
270     * stanza is send by the server. This is set to true once the last feature stanza has been
271     * parsed.
272     */
273    protected final SynchronizationPoint<Exception> lastFeaturesReceived = new SynchronizationPoint<Exception>(
274                    AbstractXMPPConnection.this, "last stream features received from server");
275
276    /**
277     * Set to success if the SASL feature has been received.
278     */
279    protected final SynchronizationPoint<XMPPException> saslFeatureReceived = new SynchronizationPoint<>(
280                    AbstractXMPPConnection.this, "SASL mechanisms stream feature from server");
281
282
283    /**
284     * A synchronization point which is successful if this connection has received the closing
285     * stream element from the remote end-point, i.e. the server.
286     */
287    protected final SynchronizationPoint<Exception> closingStreamReceived = new SynchronizationPoint<>(
288                    this, "stream closing element received");
289
290    /**
291     * The SASLAuthentication manager that is responsible for authenticating with the server.
292     */
293    protected final SASLAuthentication saslAuthentication;
294
295    /**
296     * A number to uniquely identify connections that are created. This is distinct from the
297     * connection ID, which is a value sent by the server once a connection is made.
298     */
299    protected final int connectionCounterValue = connectionCounter.getAndIncrement();
300
301    /**
302     * Holds the initial configuration used while creating the connection.
303     */
304    protected final ConnectionConfiguration config;
305
306    /**
307     * Defines how the from attribute of outgoing stanzas should be handled.
308     */
309    private FromMode fromMode = FromMode.OMITTED;
310
311    protected XMPPInputOutputStream compressionHandler;
312
313    private ParsingExceptionCallback parsingExceptionCallback = SmackConfiguration.getDefaultParsingExceptionCallback();
314
315    /**
316     * A cached thread pool executor service with custom thread factory to set meaningful names on the threads and set
317     * them 'daemon'.
318     */
319    private static final ExecutorService CACHED_EXECUTOR_SERVICE = Executors.newCachedThreadPool(new ThreadFactory() {
320        @Override
321        public Thread newThread(Runnable runnable) {
322            Thread thread = new Thread(runnable);
323            thread.setName("Smack Cached Executor");
324            thread.setDaemon(true);
325            return thread;
326        }
327    });
328
329    private static final AsyncButOrdered<AbstractXMPPConnection> ASYNC_BUT_ORDERED = new AsyncButOrdered<>();
330
331    /**
332     * The used host to establish the connection to
333     */
334    protected String host;
335
336    /**
337     * The used port to establish the connection to
338     */
339    protected int port;
340
341    /**
342     * Flag that indicates if the user is currently authenticated with the server.
343     */
344    protected boolean authenticated = false;
345
346    /**
347     * Flag that indicates if the user was authenticated with the server when the connection
348     * to the server was closed (abruptly or not).
349     */
350    protected boolean wasAuthenticated = false;
351
352    private final Map<String, IQRequestHandler> setIqRequestHandler = new HashMap<>();
353    private final Map<String, IQRequestHandler> getIqRequestHandler = new HashMap<>();
354
355    /**
356     * Create a new XMPPConnection to an XMPP server.
357     *
358     * @param configuration The configuration which is used to establish the connection.
359     */
360    protected AbstractXMPPConnection(ConnectionConfiguration configuration) {
361        saslAuthentication = new SASLAuthentication(this, configuration);
362        config = configuration;
363        SmackDebuggerFactory debuggerFactory = configuration.getDebuggerFactory();
364        if (debuggerFactory != null) {
365            debugger = debuggerFactory.create(this);
366        } else {
367            debugger = null;
368        }
369        // Notify listeners that a new connection has been established
370        for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) {
371            listener.connectionCreated(this);
372        }
373    }
374
375    /**
376     * Get the connection configuration used by this connection.
377     *
378     * @return the connection configuration.
379     */
380    public ConnectionConfiguration getConfiguration() {
381        return config;
382    }
383
384    @Override
385    public DomainBareJid getXMPPServiceDomain() {
386        if (xmppServiceDomain != null) {
387            return xmppServiceDomain;
388        }
389        return config.getXMPPServiceDomain();
390    }
391
392    @Override
393    public String getHost() {
394        return host;
395    }
396
397    @Override
398    public int getPort() {
399        return port;
400    }
401
402    @Override
403    public abstract boolean isSecureConnection();
404
405    protected abstract void sendStanzaInternal(Stanza packet) throws NotConnectedException, InterruptedException;
406
407    @Override
408    public boolean trySendStanza(Stanza stanza) throws NotConnectedException {
409        // Default implementation which falls back to sendStanza() as mentioned in the methods javadoc. May be
410        // overwritten by subclasses.
411        try {
412            sendStanza(stanza);
413        } catch (InterruptedException e) {
414            LOGGER.log(Level.FINER,
415                            "Thread blocked in fallback implementation of trySendStanza(Stanza) was interrupted", e);
416            return false;
417        }
418        return true;
419    }
420
421    @Override
422    public boolean trySendStanza(Stanza stanza, long timeout, TimeUnit unit)
423                    throws NotConnectedException, InterruptedException {
424        // Default implementation which falls back to sendStanza() as mentioned in the methods javadoc. May be
425        // overwritten by subclasses.
426        sendStanza(stanza);
427        return true;
428    }
429
430    @Override
431    public abstract void sendNonza(Nonza element) throws NotConnectedException, InterruptedException;
432
433    @Override
434    public abstract boolean isUsingCompression();
435
436    /**
437     * Establishes a connection to the XMPP server. It basically
438     * creates and maintains a connection to the server.
439     * <p>
440     * Listeners will be preserved from a previous connection.
441     * </p>
442     *
443     * @throws XMPPException if an error occurs on the XMPP protocol level.
444     * @throws SmackException if an error occurs somewhere else besides XMPP protocol level.
445     * @throws IOException
446     * @return a reference to this object, to chain <code>connect()</code> with <code>login()</code>.
447     * @throws InterruptedException
448     */
449    public synchronized AbstractXMPPConnection connect() throws SmackException, IOException, XMPPException, InterruptedException {
450        // Check if not already connected
451        throwAlreadyConnectedExceptionIfAppropriate();
452
453        // Reset the connection state
454        saslAuthentication.init();
455        saslFeatureReceived.init();
456        lastFeaturesReceived.init();
457        tlsHandled.init();
458        streamId = null;
459
460        // Perform the actual connection to the XMPP service
461        connectInternal();
462
463        // If TLS is required but the server doesn't offer it, disconnect
464        // from the server and throw an error. First check if we've already negotiated TLS
465        // and are secure, however (features get parsed a second time after TLS is established).
466        if (!isSecureConnection() && getConfiguration().getSecurityMode() == SecurityMode.required) {
467            shutdown();
468            throw new SecurityRequiredByClientException();
469        }
470
471        // Make note of the fact that we're now connected.
472        connected = true;
473        callConnectionConnectedListener();
474
475        return this;
476    }
477
478    /**
479     * Abstract method that concrete subclasses of XMPPConnection need to implement to perform their
480     * way of XMPP connection establishment. Implementations are required to perform an automatic
481     * login if the previous connection state was logged (authenticated).
482     *
483     * @throws SmackException
484     * @throws IOException
485     * @throws XMPPException
486     * @throws InterruptedException
487     */
488    protected abstract void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException;
489
490    private String usedUsername, usedPassword;
491
492    /**
493     * The resourcepart used for this connection. May not be the resulting resourcepart if it's null or overridden by the XMPP service.
494     */
495    private Resourcepart usedResource;
496
497    /**
498     * Logs in to the server using the strongest SASL mechanism supported by
499     * the server. If more than the connection's default stanza timeout elapses in each step of the
500     * authentication process without a response from the server, a
501     * {@link SmackException.NoResponseException} will be thrown.
502     * <p>
503     * Before logging in (i.e. authenticate) to the server the connection must be connected
504     * by calling {@link #connect}.
505     * </p>
506     * <p>
507     * It is possible to log in without sending an initial available presence by using
508     * {@link ConnectionConfiguration.Builder#setSendPresence(boolean)}.
509     * Finally, if you want to not pass a password and instead use a more advanced mechanism
510     * while using SASL then you may be interested in using
511     * {@link ConnectionConfiguration.Builder#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}.
512     * For more advanced login settings see {@link ConnectionConfiguration}.
513     * </p>
514     *
515     * @throws XMPPException if an error occurs on the XMPP protocol level.
516     * @throws SmackException if an error occurs somewhere else besides XMPP protocol level.
517     * @throws IOException if an I/O error occurs during login.
518     * @throws InterruptedException
519     */
520    public synchronized void login() throws XMPPException, SmackException, IOException, InterruptedException {
521        // The previously used username, password and resource take over precedence over the
522        // ones from the connection configuration
523        CharSequence username = usedUsername != null ? usedUsername : config.getUsername();
524        String password = usedPassword != null ? usedPassword : config.getPassword();
525        Resourcepart resource = usedResource != null ? usedResource : config.getResource();
526        login(username, password, resource);
527    }
528
529    /**
530     * Same as {@link #login(CharSequence, String, Resourcepart)}, but takes the resource from the connection
531     * configuration.
532     *
533     * @param username
534     * @param password
535     * @throws XMPPException
536     * @throws SmackException
537     * @throws IOException
538     * @throws InterruptedException
539     * @see #login
540     */
541    public synchronized void login(CharSequence username, String password) throws XMPPException, SmackException,
542                    IOException, InterruptedException {
543        login(username, password, config.getResource());
544    }
545
546    /**
547     * Login with the given username (authorization identity). You may omit the password if a callback handler is used.
548     * If resource is null, then the server will generate one.
549     *
550     * @param username
551     * @param password
552     * @param resource
553     * @throws XMPPException
554     * @throws SmackException
555     * @throws IOException
556     * @throws InterruptedException
557     * @see #login
558     */
559    public synchronized void login(CharSequence username, String password, Resourcepart resource) throws XMPPException,
560                    SmackException, IOException, InterruptedException {
561        if (!config.allowNullOrEmptyUsername) {
562            StringUtils.requireNotNullNorEmpty(username, "Username must not be null nor empty");
563        }
564        throwNotConnectedExceptionIfAppropriate("Did you call connect() before login()?");
565        throwAlreadyLoggedInExceptionIfAppropriate();
566        usedUsername = username != null ? username.toString() : null;
567        usedPassword = password;
568        usedResource = resource;
569        loginInternal(usedUsername, usedPassword, usedResource);
570    }
571
572    protected abstract void loginInternal(String username, String password, Resourcepart resource)
573                    throws XMPPException, SmackException, IOException, InterruptedException;
574
575    @Override
576    public final boolean isConnected() {
577        return connected;
578    }
579
580    @Override
581    public final boolean isAuthenticated() {
582        return authenticated;
583    }
584
585    @Override
586    public final EntityFullJid getUser() {
587        return user;
588    }
589
590    @Override
591    public String getStreamId() {
592        if (!isConnected()) {
593            return null;
594        }
595        return streamId;
596    }
597
598    protected void bindResourceAndEstablishSession(Resourcepart resource) throws XMPPErrorException,
599                    SmackException, InterruptedException {
600
601        // Wait until either:
602        // - the servers last features stanza has been parsed
603        // - the timeout occurs
604        LOGGER.finer("Waiting for last features to be received before continuing with resource binding");
605        lastFeaturesReceived.checkIfSuccessOrWait();
606
607
608        if (!hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
609            // Server never offered resource binding, which is REQUIRED in XMPP client and
610            // server implementations as per RFC6120 7.2
611            throw new ResourceBindingNotOfferedException();
612        }
613
614        // Resource binding, see RFC6120 7.
615        // Note that we can not use IQReplyFilter here, since the users full JID is not yet
616        // available. It will become available right after the resource has been successfully bound.
617        Bind bindResource = Bind.newSet(resource);
618        StanzaCollector packetCollector = createStanzaCollectorAndSend(new StanzaIdFilter(bindResource), bindResource);
619        Bind response = packetCollector.nextResultOrThrow();
620        // Set the connections user to the result of resource binding. It is important that we don't infer the user
621        // from the login() arguments and the configurations service name, as, for example, when SASL External is used,
622        // the username is not given to login but taken from the 'external' certificate.
623        user = response.getJid();
624        xmppServiceDomain = user.asDomainBareJid();
625
626        Session.Feature sessionFeature = getFeature(Session.ELEMENT, Session.NAMESPACE);
627        // Only bind the session if it's announced as stream feature by the server, is not optional and not disabled
628        // For more information see http://tools.ietf.org/html/draft-cridland-xmpp-session-01
629        if (sessionFeature != null && !sessionFeature.isOptional()) {
630            Session session = new Session();
631            packetCollector = createStanzaCollectorAndSend(new StanzaIdFilter(session), session);
632            packetCollector.nextResultOrThrow();
633        }
634    }
635
636    protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException, InterruptedException {
637        // Indicate that we're now authenticated.
638        this.authenticated = true;
639
640        // If debugging is enabled, change the the debug window title to include the
641        // name we are now logged-in as.
642        // If DEBUG was set to true AFTER the connection was created the debugger
643        // will be null
644        if (debugger != null) {
645            debugger.userHasLogged(user);
646        }
647        callConnectionAuthenticatedListener(resumed);
648
649        // Set presence to online. It is important that this is done after
650        // callConnectionAuthenticatedListener(), as this call will also
651        // eventually load the roster. And we should load the roster before we
652        // send the initial presence.
653        if (config.isSendPresence() && !resumed) {
654            sendStanza(new Presence(Presence.Type.available));
655        }
656    }
657
658    @Override
659    public final boolean isAnonymous() {
660        return isAuthenticated() && SASLAnonymous.NAME.equals(getUsedSaslMechansism());
661    }
662
663    /**
664     * Get the name of the SASL mechanism that was used to authenticate this connection. This returns the name of
665     * mechanism which was used the last time this connection was authenticated, and will return <code>null</code> if
666     * this connection was not authenticated before.
667     *
668     * @return the name of the used SASL mechanism.
669     * @since 4.2
670     */
671    public final String getUsedSaslMechansism() {
672        return saslAuthentication.getNameOfLastUsedSaslMechansism();
673    }
674
675    private DomainBareJid xmppServiceDomain;
676
677    protected List<HostAddress> hostAddresses;
678
679    /**
680     * Populates {@link #hostAddresses} with the resolved addresses or with the configured host address. If no host
681     * address was configured and all lookups failed, for example with NX_DOMAIN, then {@link #hostAddresses} will be
682     * populated with the empty list.
683     *
684     * @return a list of host addresses where DNS (SRV) RR resolution failed.
685     */
686    protected List<HostAddress> populateHostAddresses() {
687        List<HostAddress> failedAddresses = new LinkedList<>();
688        if (config.hostAddress != null) {
689            hostAddresses = new ArrayList<>(1);
690            HostAddress hostAddress = new HostAddress(config.port, config.hostAddress);
691            hostAddresses.add(hostAddress);
692        }
693        else if (config.host != null) {
694            hostAddresses = new ArrayList<>(1);
695            HostAddress hostAddress = DNSUtil.getDNSResolver().lookupHostAddress(config.host, config.port, failedAddresses, config.getDnssecMode());
696            if (hostAddress != null) {
697                hostAddresses.add(hostAddress);
698            }
699        } else {
700            // N.B.: Important to use config.serviceName and not AbstractXMPPConnection.serviceName
701            DnsName dnsName = DnsName.from(config.getXMPPServiceDomain());
702            hostAddresses = DNSUtil.resolveXMPPServiceDomain(dnsName, failedAddresses, config.getDnssecMode());
703        }
704        // Either the populated host addresses are not empty *or* there must be at least one failed address.
705        assert (!hostAddresses.isEmpty() || !failedAddresses.isEmpty());
706        return failedAddresses;
707    }
708
709    protected Lock getConnectionLock() {
710        return connectionLock;
711    }
712
713    protected void throwNotConnectedExceptionIfAppropriate() throws NotConnectedException {
714        throwNotConnectedExceptionIfAppropriate(null);
715    }
716
717    protected void throwNotConnectedExceptionIfAppropriate(String optionalHint) throws NotConnectedException {
718        if (!isConnected()) {
719            throw new NotConnectedException(optionalHint);
720        }
721    }
722
723    protected void throwAlreadyConnectedExceptionIfAppropriate() throws AlreadyConnectedException {
724        if (isConnected()) {
725            throw new AlreadyConnectedException();
726        }
727    }
728
729    protected void throwAlreadyLoggedInExceptionIfAppropriate() throws AlreadyLoggedInException {
730        if (isAuthenticated()) {
731            throw new AlreadyLoggedInException();
732        }
733    }
734
735    // TODO: This method should be final.
736    @Override
737    public void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
738        Objects.requireNonNull(stanza, "Stanza must not be null");
739        assert (stanza instanceof Message || stanza instanceof Presence || stanza instanceof IQ);
740
741        throwNotConnectedExceptionIfAppropriate();
742        switch (fromMode) {
743        case OMITTED:
744            stanza.setFrom((Jid) null);
745            break;
746        case USER:
747            stanza.setFrom(getUser());
748            break;
749        case UNCHANGED:
750        default:
751            break;
752        }
753        // Invoke interceptors for the new stanza that is about to be sent. Interceptors may modify
754        // the content of the stanza.
755        firePacketInterceptors(stanza);
756        sendStanzaInternal(stanza);
757    }
758
759    /**
760     * Returns the SASLAuthentication manager that is responsible for authenticating with
761     * the server.
762     *
763     * @return the SASLAuthentication manager that is responsible for authenticating with
764     *         the server.
765     */
766    protected SASLAuthentication getSASLAuthentication() {
767        return saslAuthentication;
768    }
769
770    /**
771     * Closes the connection by setting presence to unavailable then closing the connection to
772     * the XMPP server. The XMPPConnection can still be used for connecting to the server
773     * again.
774     *
775     */
776    public void disconnect() {
777        Presence unavailablePresence = null;
778        if (isAuthenticated()) {
779            unavailablePresence = new Presence(Presence.Type.unavailable);
780        }
781        try {
782            disconnect(unavailablePresence);
783        }
784        catch (NotConnectedException e) {
785            LOGGER.log(Level.FINEST, "Connection is already disconnected", e);
786        }
787    }
788
789    /**
790     * Closes the connection. A custom unavailable presence is sent to the server, followed
791     * by closing the stream. The XMPPConnection can still be used for connecting to the server
792     * again. A custom unavailable presence is useful for communicating offline presence
793     * information such as "On vacation". Typically, just the status text of the presence
794     * stanza is set with online information, but most XMPP servers will deliver the full
795     * presence stanza with whatever data is set.
796     *
797     * @param unavailablePresence the optional presence stanza to send during shutdown.
798     * @throws NotConnectedException
799     */
800    public synchronized void disconnect(Presence unavailablePresence) throws NotConnectedException {
801        if (unavailablePresence != null) {
802            try {
803                sendStanza(unavailablePresence);
804            } catch (InterruptedException e) {
805                LOGGER.log(Level.FINE,
806                        "Was interrupted while sending unavailable presence. Continuing to disconnect the connection",
807                        e);
808            }
809        }
810        shutdown();
811        callConnectionClosedListener();
812    }
813
814    /**
815     * Shuts the current connection down.
816     */
817    protected abstract void shutdown();
818
819    @Override
820    public void addConnectionListener(ConnectionListener connectionListener) {
821        if (connectionListener == null) {
822            return;
823        }
824        connectionListeners.add(connectionListener);
825    }
826
827    @Override
828    public void removeConnectionListener(ConnectionListener connectionListener) {
829        connectionListeners.remove(connectionListener);
830    }
831
832    @Override
833    public <I extends IQ> I sendIqRequestAndWaitForResponse(IQ request)
834            throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
835        StanzaCollector collector = createStanzaCollectorAndSend(request);
836        IQ resultResponse = collector.nextResultOrThrow();
837        @SuppressWarnings("unchecked")
838        I concreteResultResponse = (I) resultResponse;
839        return concreteResultResponse;
840    }
841
842    @Override
843    public StanzaCollector createStanzaCollectorAndSend(IQ packet) throws NotConnectedException, InterruptedException {
844        StanzaFilter packetFilter = new IQReplyFilter(packet, this);
845        // Create the packet collector before sending the packet
846        StanzaCollector packetCollector = createStanzaCollectorAndSend(packetFilter, packet);
847        return packetCollector;
848    }
849
850    @Override
851    public StanzaCollector createStanzaCollectorAndSend(StanzaFilter packetFilter, Stanza packet)
852                    throws NotConnectedException, InterruptedException {
853        StanzaCollector.Configuration configuration = StanzaCollector.newConfiguration()
854                        .setStanzaFilter(packetFilter)
855                        .setRequest(packet);
856        // Create the packet collector before sending the packet
857        StanzaCollector packetCollector = createStanzaCollector(configuration);
858        try {
859            // Now we can send the packet as the collector has been created
860            sendStanza(packet);
861        }
862        catch (InterruptedException | NotConnectedException | RuntimeException e) {
863            packetCollector.cancel();
864            throw e;
865        }
866        return packetCollector;
867    }
868
869    @Override
870    public StanzaCollector createStanzaCollector(StanzaFilter packetFilter) {
871        StanzaCollector.Configuration configuration = StanzaCollector.newConfiguration().setStanzaFilter(packetFilter);
872        return createStanzaCollector(configuration);
873    }
874
875    @Override
876    public StanzaCollector createStanzaCollector(StanzaCollector.Configuration configuration) {
877        StanzaCollector collector = new StanzaCollector(this, configuration);
878        // Add the collector to the list of active collectors.
879        collectors.add(collector);
880        return collector;
881    }
882
883    @Override
884    public void removeStanzaCollector(StanzaCollector collector) {
885        collectors.remove(collector);
886    }
887
888    @Override
889    public void addSyncStanzaListener(StanzaListener packetListener, StanzaFilter packetFilter) {
890        if (packetListener == null) {
891            throw new NullPointerException("Packet listener is null.");
892        }
893        ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter);
894        synchronized (syncRecvListeners) {
895            syncRecvListeners.put(packetListener, wrapper);
896        }
897    }
898
899    @Override
900    public boolean removeSyncStanzaListener(StanzaListener packetListener) {
901        synchronized (syncRecvListeners) {
902            return syncRecvListeners.remove(packetListener) != null;
903        }
904    }
905
906    @Override
907    public void addAsyncStanzaListener(StanzaListener packetListener, StanzaFilter packetFilter) {
908        if (packetListener == null) {
909            throw new NullPointerException("Packet listener is null.");
910        }
911        ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter);
912        synchronized (asyncRecvListeners) {
913            asyncRecvListeners.put(packetListener, wrapper);
914        }
915    }
916
917    @Override
918    public boolean removeAsyncStanzaListener(StanzaListener packetListener) {
919        synchronized (asyncRecvListeners) {
920            return asyncRecvListeners.remove(packetListener) != null;
921        }
922    }
923
924    @Override
925    public void addStanzaSendingListener(StanzaListener packetListener, StanzaFilter packetFilter) {
926        if (packetListener == null) {
927            throw new NullPointerException("Packet listener is null.");
928        }
929        ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter);
930        synchronized (sendListeners) {
931            sendListeners.put(packetListener, wrapper);
932        }
933    }
934
935    @Override
936    public void removeStanzaSendingListener(StanzaListener packetListener) {
937        synchronized (sendListeners) {
938            sendListeners.remove(packetListener);
939        }
940    }
941
942    /**
943     * Process all stanza listeners for sending packets.
944     * <p>
945     * Compared to {@link #firePacketInterceptors(Stanza)}, the listeners will be invoked in a new thread.
946     * </p>
947     *
948     * @param packet the stanza to process.
949     */
950    @SuppressWarnings("javadoc")
951    protected void firePacketSendingListeners(final Stanza packet) {
952        final SmackDebugger debugger = this.debugger;
953        if (debugger != null) {
954            debugger.onOutgoingStreamElement(packet);
955        }
956
957        final List<StanzaListener> listenersToNotify = new LinkedList<>();
958        synchronized (sendListeners) {
959            for (ListenerWrapper listenerWrapper : sendListeners.values()) {
960                if (listenerWrapper.filterMatches(packet)) {
961                    listenersToNotify.add(listenerWrapper.getListener());
962                }
963            }
964        }
965        if (listenersToNotify.isEmpty()) {
966            return;
967        }
968        // Notify in a new thread, because we can
969        asyncGo(new Runnable() {
970            @Override
971            public void run() {
972                for (StanzaListener listener : listenersToNotify) {
973                    try {
974                        listener.processStanza(packet);
975                    }
976                    catch (Exception e) {
977                        LOGGER.log(Level.WARNING, "Sending listener threw exception", e);
978                        continue;
979                    }
980                }
981            }
982        });
983    }
984
985    @Override
986    public void addStanzaInterceptor(StanzaListener packetInterceptor,
987            StanzaFilter packetFilter) {
988        if (packetInterceptor == null) {
989            throw new NullPointerException("Packet interceptor is null.");
990        }
991        InterceptorWrapper interceptorWrapper = new InterceptorWrapper(packetInterceptor, packetFilter);
992        synchronized (interceptors) {
993            interceptors.put(packetInterceptor, interceptorWrapper);
994        }
995    }
996
997    @Override
998    public void removeStanzaInterceptor(StanzaListener packetInterceptor) {
999        synchronized (interceptors) {
1000            interceptors.remove(packetInterceptor);
1001        }
1002    }
1003
1004    /**
1005     * Process interceptors. Interceptors may modify the stanza that is about to be sent.
1006     * Since the thread that requested to send the stanza will invoke all interceptors, it
1007     * is important that interceptors perform their work as soon as possible so that the
1008     * thread does not remain blocked for a long period.
1009     *
1010     * @param packet the stanza that is going to be sent to the server
1011     */
1012    private void firePacketInterceptors(Stanza packet) {
1013        List<StanzaListener> interceptorsToInvoke = new LinkedList<>();
1014        synchronized (interceptors) {
1015            for (InterceptorWrapper interceptorWrapper : interceptors.values()) {
1016                if (interceptorWrapper.filterMatches(packet)) {
1017                    interceptorsToInvoke.add(interceptorWrapper.getInterceptor());
1018                }
1019            }
1020        }
1021        for (StanzaListener interceptor : interceptorsToInvoke) {
1022            try {
1023                interceptor.processStanza(packet);
1024            } catch (Exception e) {
1025                LOGGER.log(Level.SEVERE, "Packet interceptor threw exception", e);
1026            }
1027        }
1028    }
1029
1030    /**
1031     * Initialize the {@link #debugger}. You can specify a customized {@link SmackDebugger}
1032     * by setup the system property <code>smack.debuggerClass</code> to the implementation.
1033     *
1034     * @throws IllegalStateException if the reader or writer isn't yet initialized.
1035     * @throws IllegalArgumentException if the SmackDebugger can't be loaded.
1036     */
1037    protected void initDebugger() {
1038        if (reader == null || writer == null) {
1039            throw new NullPointerException("Reader or writer isn't initialized.");
1040        }
1041        // If debugging is enabled, we open a window and write out all network traffic.
1042        if (debugger != null) {
1043            // Obtain new reader and writer from the existing debugger
1044            reader = debugger.newConnectionReader(reader);
1045            writer = debugger.newConnectionWriter(writer);
1046        }
1047    }
1048
1049    @Override
1050    public long getReplyTimeout() {
1051        return replyTimeout;
1052    }
1053
1054    @Override
1055    public void setReplyTimeout(long timeout) {
1056        replyTimeout = timeout;
1057    }
1058
1059    private SmackConfiguration.UnknownIqRequestReplyMode unknownIqRequestReplyMode = SmackConfiguration.getUnknownIqRequestReplyMode();
1060
1061    /**
1062     * Set how Smack behaves when an unknown IQ request has been received.
1063     *
1064     * @param unknownIqRequestReplyMode reply mode.
1065     */
1066    public void setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode unknownIqRequestReplyMode) {
1067        this.unknownIqRequestReplyMode = Objects.requireNonNull(unknownIqRequestReplyMode, "Mode must not be null");
1068    }
1069
1070    protected final NonzaCallback.Builder buildNonzaCallback() {
1071        return new NonzaCallback.Builder(this);
1072    }
1073
1074    protected <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(Nonza nonza, Class<SN> successNonzaClass,
1075                    Class<FN> failedNonzaClass)
1076                    throws NoResponseException, NotConnectedException, InterruptedException, FailedNonzaException {
1077        NonzaCallback.Builder builder = buildNonzaCallback();
1078        SN successNonza = NonzaCallback.sendAndWaitForResponse(builder, nonza, successNonzaClass, failedNonzaClass);
1079        return successNonza;
1080    }
1081
1082    protected final void parseAndProcessNonza(XmlPullParser parser) throws SmackException {
1083        final String element = parser.getName();
1084        final String namespace = parser.getNamespace();
1085        final String key = XmppStringUtils.generateKey(element, namespace);
1086
1087        NonzaProvider<? extends Nonza> nonzaProvider = ProviderManager.getNonzaProvider(key);
1088        if (nonzaProvider == null) {
1089            LOGGER.severe("Unknown nonza: " + key);
1090            return;
1091        }
1092
1093        NonzaCallback nonzaCallback;
1094        synchronized (nonzaCallbacks) {
1095            nonzaCallback = nonzaCallbacks.get(key);
1096        }
1097        if (nonzaCallback == null) {
1098            LOGGER.info("No nonza callback for " + key);
1099            return;
1100        }
1101
1102        Nonza nonza;
1103        try {
1104            nonza = nonzaProvider.parse(parser);
1105        }
1106        catch (Exception e) {
1107            throw new SmackException(e);
1108        }
1109
1110        nonzaCallback.onNonzaReceived(nonza);
1111    }
1112
1113    protected void parseAndProcessStanza(XmlPullParser parser) throws Exception {
1114        ParserUtils.assertAtStartTag(parser);
1115        int parserDepth = parser.getDepth();
1116        Stanza stanza = null;
1117        try {
1118            stanza = PacketParserUtils.parseStanza(parser);
1119        }
1120        catch (Exception e) {
1121            CharSequence content = PacketParserUtils.parseContentDepth(parser,
1122                            parserDepth);
1123            UnparseableStanza message = new UnparseableStanza(content, e);
1124            ParsingExceptionCallback callback = getParsingExceptionCallback();
1125            if (callback != null) {
1126                callback.handleUnparsableStanza(message);
1127            }
1128        }
1129        ParserUtils.assertAtEndTag(parser);
1130        if (stanza != null) {
1131            processStanza(stanza);
1132        }
1133    }
1134
1135    /**
1136     * Processes a stanza after it's been fully parsed by looping through the installed
1137     * stanza collectors and listeners and letting them examine the stanza to see if
1138     * they are a match with the filter.
1139     *
1140     * @param stanza the stanza to process.
1141     * @throws InterruptedException
1142     */
1143    protected void processStanza(final Stanza stanza) throws InterruptedException {
1144        assert (stanza != null);
1145
1146        final SmackDebugger debugger = this.debugger;
1147        if (debugger != null) {
1148            debugger.onIncomingStreamElement(stanza);
1149        }
1150
1151        lastStanzaReceived = System.currentTimeMillis();
1152        // Deliver the incoming packet to listeners.
1153        invokeStanzaCollectorsAndNotifyRecvListeners(stanza);
1154    }
1155
1156    /**
1157     * Invoke {@link StanzaCollector#processStanza(Stanza)} for every
1158     * StanzaCollector with the given packet. Also notify the receive listeners with a matching stanza filter about the packet.
1159     * <p>
1160     * This method will be invoked by the connections incoming processing thread which may be shared across multiple connections and
1161     * thus it is important that no user code, e.g. in form of a callback, is invoked by this method. For the same reason,
1162     * this method must not block for an extended period of time.
1163     * </p>
1164     *
1165     * @param packet the stanza to notify the StanzaCollectors and receive listeners about.
1166     */
1167    protected void invokeStanzaCollectorsAndNotifyRecvListeners(final Stanza packet) {
1168        if (packet instanceof IQ) {
1169            final IQ iq = (IQ) packet;
1170            if (iq.isRequestIQ()) {
1171                final String key = XmppStringUtils.generateKey(iq.getChildElementName(), iq.getChildElementNamespace());
1172                IQRequestHandler iqRequestHandler;
1173                final IQ.Type type = iq.getType();
1174                switch (type) {
1175                case set:
1176                    synchronized (setIqRequestHandler) {
1177                        iqRequestHandler = setIqRequestHandler.get(key);
1178                    }
1179                    break;
1180                case get:
1181                    synchronized (getIqRequestHandler) {
1182                        iqRequestHandler = getIqRequestHandler.get(key);
1183                    }
1184                    break;
1185                default:
1186                    throw new IllegalStateException("Should only encounter IQ type 'get' or 'set'");
1187                }
1188                if (iqRequestHandler == null) {
1189                    StanzaError.Condition replyCondition;
1190                    switch (unknownIqRequestReplyMode) {
1191                    case doNotReply:
1192                        return;
1193                    case replyFeatureNotImplemented:
1194                        replyCondition = StanzaError.Condition.feature_not_implemented;
1195                        break;
1196                    case replyServiceUnavailable:
1197                        replyCondition = StanzaError.Condition.service_unavailable;
1198                        break;
1199                    default:
1200                        throw new AssertionError();
1201                    }
1202
1203                    // If the IQ stanza is of type "get" or "set" with no registered IQ request handler, then answer an
1204                    // IQ of type 'error' with condition 'service-unavailable'.
1205                    final ErrorIQ errorIQ = IQ.createErrorResponse(iq, StanzaError.getBuilder((
1206                                    replyCondition)));
1207                    // Use async sendStanza() here, since if sendStanza() would block, then some connections, e.g.
1208                    // XmppNioTcpConnection, would deadlock, as this operation is performed in the same thread that is
1209                    asyncGo(() -> {
1210                        try {
1211                            sendStanza(errorIQ);
1212                        }
1213                        catch (InterruptedException | NotConnectedException e) {
1214                            LOGGER.log(Level.WARNING, "Exception while sending error IQ to unkown IQ request", e);
1215                        }
1216                    });
1217                } else {
1218                    Executor executorService = null;
1219                    switch (iqRequestHandler.getMode()) {
1220                    case sync:
1221                        executorService = ASYNC_BUT_ORDERED.asExecutorFor(this);
1222                        break;
1223                    case async:
1224                        executorService = CACHED_EXECUTOR_SERVICE;
1225                        break;
1226                    }
1227                    final IQRequestHandler finalIqRequestHandler = iqRequestHandler;
1228                    executorService.execute(new Runnable() {
1229                        @Override
1230                        public void run() {
1231                            IQ response = finalIqRequestHandler.handleIQRequest(iq);
1232                            if (response == null) {
1233                                // It is not ideal if the IQ request handler does not return an IQ response, because RFC
1234                                // 6120 § 8.1.2 does specify that a response is mandatory. But some APIs, mostly the
1235                                // file transfer one, does not always return a result, so we need to handle this case.
1236                                // Also sometimes a request handler may decide that it's better to not send a response,
1237                                // e.g. to avoid presence leaks.
1238                                return;
1239                            }
1240                            try {
1241                                sendStanza(response);
1242                            }
1243                            catch (InterruptedException | NotConnectedException e) {
1244                                LOGGER.log(Level.WARNING, "Exception while sending response to IQ request", e);
1245                            }
1246                        }
1247                    });
1248                }
1249                // The following returns makes it impossible for packet listeners and collectors to
1250                // filter for IQ request stanzas, i.e. IQs of type 'set' or 'get'. This is the
1251                // desired behavior.
1252                return;
1253            }
1254        }
1255
1256        // First handle the async recv listeners. Note that this code is very similar to what follows a few lines below,
1257        // the only difference is that asyncRecvListeners is used here and that the packet listeners are started in
1258        // their own thread.
1259        final Collection<StanzaListener> listenersToNotify = new LinkedList<>();
1260        synchronized (asyncRecvListeners) {
1261            for (ListenerWrapper listenerWrapper : asyncRecvListeners.values()) {
1262                if (listenerWrapper.filterMatches(packet)) {
1263                    listenersToNotify.add(listenerWrapper.getListener());
1264                }
1265            }
1266        }
1267
1268        for (final StanzaListener listener : listenersToNotify) {
1269            asyncGo(new Runnable() {
1270                @Override
1271                public void run() {
1272                    try {
1273                        listener.processStanza(packet);
1274                    } catch (Exception e) {
1275                        LOGGER.log(Level.SEVERE, "Exception in async packet listener", e);
1276                    }
1277                }
1278            });
1279        }
1280
1281        // Loop through all collectors and notify the appropriate ones.
1282        for (StanzaCollector collector : collectors) {
1283            collector.processStanza(packet);
1284        }
1285
1286        // Notify the receive listeners interested in the packet
1287        listenersToNotify.clear();
1288        synchronized (syncRecvListeners) {
1289            for (ListenerWrapper listenerWrapper : syncRecvListeners.values()) {
1290                if (listenerWrapper.filterMatches(packet)) {
1291                    listenersToNotify.add(listenerWrapper.getListener());
1292                }
1293            }
1294        }
1295
1296        // Decouple incoming stanza processing from listener invocation. Unlike async listeners, this uses a single
1297        // threaded executor service and therefore keeps the order.
1298        ASYNC_BUT_ORDERED.performAsyncButOrdered(this, new Runnable() {
1299            @Override
1300            public void run() {
1301                for (StanzaListener listener : listenersToNotify) {
1302                    try {
1303                        listener.processStanza(packet);
1304                    } catch (NotConnectedException e) {
1305                        LOGGER.log(Level.WARNING, "Got not connected exception, aborting", e);
1306                        break;
1307                    } catch (Exception e) {
1308                        LOGGER.log(Level.SEVERE, "Exception in packet listener", e);
1309                    }
1310                }
1311            }
1312        });
1313    }
1314
1315    /**
1316     * Sets whether the connection has already logged in the server. This method assures that the
1317     * {@link #wasAuthenticated} flag is never reset once it has ever been set.
1318     *
1319     */
1320    protected void setWasAuthenticated() {
1321        // Never reset the flag if the connection has ever been authenticated
1322        if (!wasAuthenticated) {
1323            wasAuthenticated = authenticated;
1324        }
1325    }
1326
1327    protected void callConnectionConnectedListener() {
1328        for (ConnectionListener listener : connectionListeners) {
1329            listener.connected(this);
1330        }
1331    }
1332
1333    protected void callConnectionAuthenticatedListener(boolean resumed) {
1334        for (ConnectionListener listener : connectionListeners) {
1335            try {
1336                listener.authenticated(this, resumed);
1337            } catch (Exception e) {
1338                // Catch and print any exception so we can recover
1339                // from a faulty listener and finish the shutdown process
1340                LOGGER.log(Level.SEVERE, "Exception in authenticated listener", e);
1341            }
1342        }
1343    }
1344
1345    void callConnectionClosedListener() {
1346        for (ConnectionListener listener : connectionListeners) {
1347            try {
1348                listener.connectionClosed();
1349            }
1350            catch (Exception e) {
1351                // Catch and print any exception so we can recover
1352                // from a faulty listener and finish the shutdown process
1353                LOGGER.log(Level.SEVERE, "Error in listener while closing connection", e);
1354            }
1355        }
1356    }
1357
1358    protected void callConnectionClosedOnErrorListener(Exception e) {
1359        boolean logWarning = true;
1360        if (e instanceof StreamErrorException) {
1361            StreamErrorException see = (StreamErrorException) e;
1362            if (see.getStreamError().getCondition() == StreamError.Condition.not_authorized
1363                            && wasAuthenticated) {
1364                logWarning = false;
1365                LOGGER.log(Level.FINE,
1366                                "Connection closed with not-authorized stream error after it was already authenticated. The account was likely deleted/unregistered on the server");
1367            }
1368        }
1369        if (logWarning) {
1370            LOGGER.log(Level.WARNING, "Connection " + this + " closed with error", e);
1371        }
1372        for (ConnectionListener listener : connectionListeners) {
1373            try {
1374                listener.connectionClosedOnError(e);
1375            }
1376            catch (Exception e2) {
1377                // Catch and print any exception so we can recover
1378                // from a faulty listener
1379                LOGGER.log(Level.SEVERE, "Error in listener while closing connection", e2);
1380            }
1381        }
1382    }
1383
1384    /**
1385     * A wrapper class to associate a stanza filter with a listener.
1386     */
1387    protected static class ListenerWrapper {
1388
1389        private final StanzaListener packetListener;
1390        private final StanzaFilter packetFilter;
1391
1392        /**
1393         * Create a class which associates a stanza filter with a listener.
1394         *
1395         * @param packetListener the stanza listener.
1396         * @param packetFilter the associated filter or null if it listen for all packets.
1397         */
1398        public ListenerWrapper(StanzaListener packetListener, StanzaFilter packetFilter) {
1399            this.packetListener = packetListener;
1400            this.packetFilter = packetFilter;
1401        }
1402
1403        public boolean filterMatches(Stanza packet) {
1404            return packetFilter == null || packetFilter.accept(packet);
1405        }
1406
1407        public StanzaListener getListener() {
1408            return packetListener;
1409        }
1410    }
1411
1412    /**
1413     * A wrapper class to associate a stanza filter with an interceptor.
1414     */
1415    protected static class InterceptorWrapper {
1416
1417        private final StanzaListener packetInterceptor;
1418        private final StanzaFilter packetFilter;
1419
1420        /**
1421         * Create a class which associates a stanza filter with an interceptor.
1422         *
1423         * @param packetInterceptor the interceptor.
1424         * @param packetFilter the associated filter or null if it intercepts all packets.
1425         */
1426        public InterceptorWrapper(StanzaListener packetInterceptor, StanzaFilter packetFilter) {
1427            this.packetInterceptor = packetInterceptor;
1428            this.packetFilter = packetFilter;
1429        }
1430
1431        public boolean filterMatches(Stanza packet) {
1432            return packetFilter == null || packetFilter.accept(packet);
1433        }
1434
1435        public StanzaListener getInterceptor() {
1436            return packetInterceptor;
1437        }
1438    }
1439
1440    @Override
1441    public int getConnectionCounter() {
1442        return connectionCounterValue;
1443    }
1444
1445    @Override
1446    public void setFromMode(FromMode fromMode) {
1447        this.fromMode = fromMode;
1448    }
1449
1450    @Override
1451    public FromMode getFromMode() {
1452        return this.fromMode;
1453    }
1454
1455    protected final void parseFeatures(XmlPullParser parser) throws Exception {
1456        streamFeatures.clear();
1457        final int initialDepth = parser.getDepth();
1458        while (true) {
1459            int eventType = parser.next();
1460
1461            if (eventType == XmlPullParser.START_TAG && parser.getDepth() == initialDepth + 1) {
1462                FullyQualifiedElement streamFeature = null;
1463                String name = parser.getName();
1464                String namespace = parser.getNamespace();
1465                switch (name) {
1466                case StartTls.ELEMENT:
1467                    streamFeature = PacketParserUtils.parseStartTlsFeature(parser);
1468                    break;
1469                case Mechanisms.ELEMENT:
1470                    streamFeature = new Mechanisms(PacketParserUtils.parseMechanisms(parser));
1471                    break;
1472                case Bind.ELEMENT:
1473                    streamFeature = Bind.Feature.INSTANCE;
1474                    break;
1475                case Session.ELEMENT:
1476                    streamFeature = PacketParserUtils.parseSessionFeature(parser);
1477                    break;
1478                case Compress.Feature.ELEMENT:
1479                    streamFeature = PacketParserUtils.parseCompressionFeature(parser);
1480                    break;
1481                default:
1482                    ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getStreamFeatureProvider(name, namespace);
1483                    if (provider != null) {
1484                        streamFeature = provider.parse(parser);
1485                    }
1486                    break;
1487                }
1488                if (streamFeature != null) {
1489                    addStreamFeature(streamFeature);
1490                }
1491            }
1492            else if (eventType == XmlPullParser.END_TAG && parser.getDepth() == initialDepth) {
1493                break;
1494            }
1495        }
1496    }
1497
1498    protected final void parseFeaturesAndNotify(XmlPullParser parser) throws Exception {
1499        parseFeatures(parser);
1500
1501        if (hasFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE)) {
1502            // Only proceed with SASL auth if TLS is disabled or if the server doesn't announce it
1503            if (!hasFeature(StartTls.ELEMENT, StartTls.NAMESPACE)
1504                            || config.getSecurityMode() == SecurityMode.disabled) {
1505                tlsHandled.reportSuccess();
1506                saslFeatureReceived.reportSuccess();
1507            }
1508        }
1509
1510        // If the server reported the bind feature then we are that that we did SASL and maybe
1511        // STARTTLS. We can then report that the last 'stream:features' have been parsed
1512        if (hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
1513            if (!hasFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE)
1514                            || !config.isCompressionEnabled()) {
1515                // This was was last features from the server is either it did not contain
1516                // compression or if we disabled it
1517                lastFeaturesReceived.reportSuccess();
1518            }
1519        }
1520        afterFeaturesReceived();
1521    }
1522
1523    @SuppressWarnings("unused")
1524    protected void afterFeaturesReceived() throws SecurityRequiredException, NotConnectedException, InterruptedException {
1525        // Default implementation does nothing
1526    }
1527
1528    @SuppressWarnings("unchecked")
1529    @Override
1530    public <F extends FullyQualifiedElement> F getFeature(String element, String namespace) {
1531        return (F) streamFeatures.get(XmppStringUtils.generateKey(element, namespace));
1532    }
1533
1534    @Override
1535    public boolean hasFeature(String element, String namespace) {
1536        return getFeature(element, namespace) != null;
1537    }
1538
1539    protected void addStreamFeature(FullyQualifiedElement feature) {
1540        String key = XmppStringUtils.generateKey(feature.getElementName(), feature.getNamespace());
1541        streamFeatures.put(key, feature);
1542    }
1543
1544    @Override
1545    public SmackFuture<IQ, Exception> sendIqRequestAsync(IQ request) {
1546        return sendIqRequestAsync(request, getReplyTimeout());
1547    }
1548
1549    @Override
1550    public SmackFuture<IQ, Exception> sendIqRequestAsync(IQ request, long timeout) {
1551        StanzaFilter replyFilter = new IQReplyFilter(request, this);
1552        return sendAsync(request, replyFilter, timeout);
1553    }
1554
1555    @Override
1556    public <S extends Stanza> SmackFuture<S, Exception> sendAsync(S stanza, final StanzaFilter replyFilter) {
1557        return sendAsync(stanza, replyFilter, getReplyTimeout());
1558    }
1559
1560    @SuppressWarnings("FutureReturnValueIgnored")
1561    @Override
1562    public <S extends Stanza> SmackFuture<S, Exception> sendAsync(S stanza, final StanzaFilter replyFilter, long timeout) {
1563        Objects.requireNonNull(stanza, "stanza must not be null");
1564        // While Smack allows to add PacketListeners with a PacketFilter value of 'null', we
1565        // disallow it here in the async API as it makes no sense
1566        Objects.requireNonNull(replyFilter, "replyFilter must not be null");
1567
1568        final InternalSmackFuture<S, Exception> future = new InternalSmackFuture<>();
1569
1570        final StanzaListener stanzaListener = new StanzaListener() {
1571            @Override
1572            public void processStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
1573                boolean removed = removeAsyncStanzaListener(this);
1574                if (!removed) {
1575                    // We lost a race against the "no response" handling runnable. Avoid calling the callback, as the
1576                    // exception callback will be invoked (if any).
1577                    return;
1578                }
1579                try {
1580                    XMPPErrorException.ifHasErrorThenThrow(stanza);
1581                    @SuppressWarnings("unchecked")
1582                    S s = (S) stanza;
1583                    future.setResult(s);
1584                }
1585                catch (XMPPErrorException exception) {
1586                    future.setException(exception);
1587                }
1588            }
1589        };
1590        schedule(new Runnable() {
1591            @Override
1592            public void run() {
1593                boolean removed = removeAsyncStanzaListener(stanzaListener);
1594                if (!removed) {
1595                    // We lost a race against the stanza listener, he already removed itself because he received a
1596                    // reply. There is nothing more to do here.
1597                    return;
1598                }
1599
1600                // If the packetListener got removed, then it was never run and
1601                // we never received a response, inform the exception callback
1602                Exception exception;
1603                if (!isConnected()) {
1604                    // If the connection is no longer connected, throw a not connected exception.
1605                    exception = new NotConnectedException(AbstractXMPPConnection.this, replyFilter);
1606                }
1607                else {
1608                    exception = NoResponseException.newWith(AbstractXMPPConnection.this, replyFilter);
1609                }
1610                future.setException(exception);
1611            }
1612        }, timeout, TimeUnit.MILLISECONDS);
1613
1614        addAsyncStanzaListener(stanzaListener, replyFilter);
1615        try {
1616            sendStanza(stanza);
1617        }
1618        catch (NotConnectedException | InterruptedException exception) {
1619            future.setException(exception);
1620        }
1621
1622        return future;
1623    }
1624
1625    @SuppressWarnings("FutureReturnValueIgnored")
1626    @Override
1627    public void addOneTimeSyncCallback(final StanzaListener callback, final StanzaFilter packetFilter) {
1628        final StanzaListener packetListener = new StanzaListener() {
1629            @Override
1630            public void processStanza(Stanza packet) throws NotConnectedException, InterruptedException, NotLoggedInException {
1631                try {
1632                    callback.processStanza(packet);
1633                } finally {
1634                    removeSyncStanzaListener(this);
1635                }
1636            }
1637        };
1638        addSyncStanzaListener(packetListener, packetFilter);
1639        schedule(new Runnable() {
1640            @Override
1641            public void run() {
1642                removeSyncStanzaListener(packetListener);
1643            }
1644        }, getReplyTimeout(), TimeUnit.MILLISECONDS);
1645    }
1646
1647    @Override
1648    public IQRequestHandler registerIQRequestHandler(final IQRequestHandler iqRequestHandler) {
1649        final String key = XmppStringUtils.generateKey(iqRequestHandler.getElement(), iqRequestHandler.getNamespace());
1650        switch (iqRequestHandler.getType()) {
1651        case set:
1652            synchronized (setIqRequestHandler) {
1653                return setIqRequestHandler.put(key, iqRequestHandler);
1654            }
1655        case get:
1656            synchronized (getIqRequestHandler) {
1657                return getIqRequestHandler.put(key, iqRequestHandler);
1658            }
1659        default:
1660            throw new IllegalArgumentException("Only IQ type of 'get' and 'set' allowed");
1661        }
1662    }
1663
1664    @Override
1665    public final IQRequestHandler unregisterIQRequestHandler(IQRequestHandler iqRequestHandler) {
1666        return unregisterIQRequestHandler(iqRequestHandler.getElement(), iqRequestHandler.getNamespace(),
1667                        iqRequestHandler.getType());
1668    }
1669
1670    @Override
1671    public IQRequestHandler unregisterIQRequestHandler(String element, String namespace, IQ.Type type) {
1672        final String key = XmppStringUtils.generateKey(element, namespace);
1673        switch (type) {
1674        case set:
1675            synchronized (setIqRequestHandler) {
1676                return setIqRequestHandler.remove(key);
1677            }
1678        case get:
1679            synchronized (getIqRequestHandler) {
1680                return getIqRequestHandler.remove(key);
1681            }
1682        default:
1683            throw new IllegalArgumentException("Only IQ type of 'get' and 'set' allowed");
1684        }
1685    }
1686
1687    private long lastStanzaReceived;
1688
1689    @Override
1690    public long getLastStanzaReceived() {
1691        return lastStanzaReceived;
1692    }
1693
1694    /**
1695     * Install a parsing exception callback, which will be invoked once an exception is encountered while parsing a
1696     * stanza.
1697     *
1698     * @param callback the callback to install
1699     */
1700    public void setParsingExceptionCallback(ParsingExceptionCallback callback) {
1701        parsingExceptionCallback = callback;
1702    }
1703
1704    /**
1705     * Get the current active parsing exception callback.
1706     *
1707     * @return the active exception callback or null if there is none
1708     */
1709    public ParsingExceptionCallback getParsingExceptionCallback() {
1710        return parsingExceptionCallback;
1711    }
1712
1713    @Override
1714    public final String toString() {
1715        EntityFullJid localEndpoint = getUser();
1716        String localEndpointString = (localEndpoint == null ?  "not-authenticated" : localEndpoint.toString());
1717        return getClass().getSimpleName() + '[' + localEndpointString + "] (" + getConnectionCounter() + ')';
1718    }
1719
1720    protected static void asyncGo(Runnable runnable) {
1721        CACHED_EXECUTOR_SERVICE.execute(runnable);
1722    }
1723
1724    protected static ScheduledAction schedule(Runnable runnable, long delay, TimeUnit unit) {
1725        return SMACK_REACTOR.schedule(runnable, delay, unit);
1726    }
1727
1728    protected void onStreamOpen(XmlPullParser parser) {
1729        // We found an opening stream.
1730        if ("jabber:client".equals(parser.getNamespace(null))) {
1731            streamId = parser.getAttributeValue("", "id");
1732            String reportedServerDomain = parser.getAttributeValue("", "from");
1733            assert (config.getXMPPServiceDomain().equals(reportedServerDomain));
1734        }
1735    }
1736
1737    protected void sendStreamOpen() throws NotConnectedException, InterruptedException {
1738        // If possible, provide the receiving entity of the stream open tag, i.e. the server, as much information as
1739        // possible. The 'to' attribute is *always* available. The 'from' attribute if set by the user and no external
1740        // mechanism is used to determine the local entity (user). And the 'id' attribute is available after the first
1741        // response from the server (see e.g. RFC 6120 § 9.1.1 Step 2.)
1742        CharSequence to = getXMPPServiceDomain();
1743        CharSequence from = null;
1744        CharSequence localpart = config.getUsername();
1745        if (localpart != null) {
1746            from = XmppStringUtils.completeJidFrom(localpart, to);
1747        }
1748        String id = getStreamId();
1749        sendNonza(new StreamOpen(to, from, id));
1750    }
1751
1752    public static final class SmackTlsContext {
1753        public final SSLContext sslContext;
1754        public final SmackDaneVerifier daneVerifier;
1755
1756        private SmackTlsContext(SSLContext sslContext, SmackDaneVerifier daneVerifier) {
1757            assert sslContext != null;
1758            this.sslContext = sslContext;
1759            this.daneVerifier = daneVerifier;
1760        }
1761    }
1762
1763    protected final SmackTlsContext getSmackTlsContext() throws KeyManagementException, NoSuchAlgorithmException,
1764                    CertificateException, IOException, UnrecoverableKeyException, KeyStoreException, NoSuchProviderException {
1765        SmackDaneVerifier daneVerifier = null;
1766
1767        if (config.getDnssecMode() == DnssecMode.needsDnssecAndDane) {
1768            SmackDaneProvider daneProvider = DNSUtil.getDaneProvider();
1769            if (daneProvider == null) {
1770                throw new UnsupportedOperationException("DANE enabled but no SmackDaneProvider configured");
1771            }
1772            daneVerifier = daneProvider.newInstance();
1773            if (daneVerifier == null) {
1774                throw new IllegalStateException("DANE requested but DANE provider did not return a DANE verifier");
1775            }
1776        }
1777
1778        SSLContext context = this.config.getCustomSSLContext();
1779        KeyStore ks = null;
1780        PasswordCallback pcb = null;
1781
1782        if (context == null) {
1783            final String keyStoreType = config.getKeystoreType();
1784            final CallbackHandler callbackHandler = config.getCallbackHandler();
1785            final String keystorePath = config.getKeystorePath();
1786            if ("PKCS11".equals(keyStoreType)) {
1787                try {
1788                    Constructor<?> c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class);
1789                    String pkcs11Config = "name = SmartCard\nlibrary = " + config.getPKCS11Library();
1790                    ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StringUtils.UTF8));
1791                    Provider p = (Provider) c.newInstance(config);
1792                    Security.addProvider(p);
1793                    ks = KeyStore.getInstance("PKCS11",p);
1794                    pcb = new PasswordCallback("PKCS11 Password: ",false);
1795                    callbackHandler.handle(new Callback[] {pcb});
1796                    ks.load(null,pcb.getPassword());
1797                }
1798                catch (Exception e) {
1799                    LOGGER.log(Level.WARNING, "Exception", e);
1800                    ks = null;
1801                }
1802            }
1803            else if ("Apple".equals(keyStoreType)) {
1804                ks = KeyStore.getInstance("KeychainStore","Apple");
1805                ks.load(null,null);
1806                // pcb = new PasswordCallback("Apple Keychain",false);
1807                // pcb.setPassword(null);
1808            }
1809            else if (keyStoreType != null) {
1810                ks = KeyStore.getInstance(keyStoreType);
1811                if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) {
1812                    try {
1813                        pcb = new PasswordCallback("Keystore Password: ", false);
1814                        callbackHandler.handle(new Callback[] { pcb });
1815                        ks.load(new FileInputStream(keystorePath), pcb.getPassword());
1816                    }
1817                    catch (Exception e) {
1818                        LOGGER.log(Level.WARNING, "Exception", e);
1819                        ks = null;
1820                    }
1821                } else {
1822                    ks.load(null, null);
1823                }
1824            }
1825
1826            KeyManager[] kms = null;
1827
1828            if (ks != null) {
1829                String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
1830                KeyManagerFactory kmf = null;
1831                try {
1832                    kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
1833                }
1834                catch (NoSuchAlgorithmException e) {
1835                    LOGGER.log(Level.FINE, "Could get the default KeyManagerFactory for the '"
1836                                    + keyManagerFactoryAlgorithm + "' algorithm", e);
1837                }
1838                if (kmf != null) {
1839                    try {
1840                        if (pcb == null) {
1841                            kmf.init(ks, null);
1842                        }
1843                        else {
1844                            kmf.init(ks, pcb.getPassword());
1845                            pcb.clearPassword();
1846                        }
1847                        kms = kmf.getKeyManagers();
1848                    }
1849                    catch (NullPointerException npe) {
1850                        LOGGER.log(Level.WARNING, "NullPointerException", npe);
1851                    }
1852                }
1853            }
1854
1855            // If the user didn't specify a SSLContext, use the default one
1856            context = SSLContext.getInstance("TLS");
1857
1858            final SecureRandom secureRandom = new java.security.SecureRandom();
1859            X509TrustManager customTrustManager = config.getCustomX509TrustManager();
1860
1861            if (daneVerifier != null) {
1862                // User requested DANE verification.
1863                daneVerifier.init(context, kms, customTrustManager, secureRandom);
1864            } else {
1865                TrustManager[] customTrustManagers = null;
1866                if (customTrustManager != null) {
1867                    customTrustManagers = new TrustManager[] { customTrustManager };
1868                }
1869                context.init(kms, customTrustManagers, secureRandom);
1870            }
1871        }
1872
1873        return new SmackTlsContext(context, daneVerifier);
1874    }
1875}