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}