001/** 002 * 003 * Copyright the original author or authors 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.smackx.bytestreams.socks5; 018 019import java.io.IOException; 020import java.net.InetAddress; 021import java.net.Socket; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.WeakHashMap; 029import java.util.concurrent.ConcurrentHashMap; 030import java.util.concurrent.TimeoutException; 031 032import org.jivesoftware.smack.ConnectionCreationListener; 033import org.jivesoftware.smack.Manager; 034import org.jivesoftware.smack.SmackException; 035import org.jivesoftware.smack.SmackException.FeatureNotSupportedException; 036import org.jivesoftware.smack.SmackException.NoResponseException; 037import org.jivesoftware.smack.SmackException.NotConnectedException; 038import org.jivesoftware.smack.SmackException.SmackMessageException; 039import org.jivesoftware.smack.XMPPConnection; 040import org.jivesoftware.smack.XMPPConnectionRegistry; 041import org.jivesoftware.smack.XMPPException; 042import org.jivesoftware.smack.XMPPException.XMPPErrorException; 043import org.jivesoftware.smack.packet.IQ; 044import org.jivesoftware.smack.packet.Stanza; 045import org.jivesoftware.smack.packet.StanzaError; 046import org.jivesoftware.smack.util.StringUtils; 047 048import org.jivesoftware.smackx.bytestreams.BytestreamListener; 049import org.jivesoftware.smackx.bytestreams.BytestreamManager; 050import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; 051import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; 052import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHostUsed; 053import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 054import org.jivesoftware.smackx.disco.packet.DiscoverInfo; 055import org.jivesoftware.smackx.disco.packet.DiscoverItems; 056import org.jivesoftware.smackx.disco.packet.DiscoverItems.Item; 057import org.jivesoftware.smackx.filetransfer.FileTransferManager; 058 059import org.jxmpp.jid.EntityFullJid; 060import org.jxmpp.jid.Jid; 061 062/** 063 * The Socks5BytestreamManager class handles establishing SOCKS5 Bytestreams as specified in the <a 064 * href="http://xmpp.org/extensions/xep-0065.html">XEP-0065</a>. 065 * <p> 066 * A SOCKS5 Bytestream is negotiated partly over the XMPP XML stream and partly over a separate 067 * socket. The actual transfer though takes place over a separately created socket. 068 * <p> 069 * A SOCKS5 Bytestream generally has three parties, the initiator, the target, and the stream host. 070 * The stream host is a specialized SOCKS5 proxy setup on a server, or, the initiator can act as the 071 * stream host. 072 * <p> 073 * To establish a SOCKS5 Bytestream invoke the {@link #establishSession(Jid)} method. This will 074 * negotiate a SOCKS5 Bytestream with the given target JID and return a socket. 075 * <p> 076 * If a session ID for the SOCKS5 Bytestream was already negotiated (e.g. while negotiating a file 077 * transfer) invoke {@link #establishSession(Jid, String)}. 078 * <p> 079 * To handle incoming SOCKS5 Bytestream requests add an {@link Socks5BytestreamListener} to the 080 * manager. There are two ways to add this listener. If you want to be informed about incoming 081 * SOCKS5 Bytestreams from a specific user add the listener by invoking 082 * {@link #addIncomingBytestreamListener(BytestreamListener, Jid)}. If the listener should 083 * respond to all SOCKS5 Bytestream requests invoke 084 * {@link #addIncomingBytestreamListener(BytestreamListener)}. 085 * <p> 086 * Note that the registered {@link Socks5BytestreamListener} will NOT be notified on incoming Socks5 087 * bytestream requests sent in the context of <a 088 * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See 089 * {@link FileTransferManager}) 090 * <p> 091 * If no {@link Socks5BytestreamListener}s are registered, all incoming SOCKS5 Bytestream requests 092 * will be rejected by returning a <not-acceptable/> error to the initiator. 093 * 094 * @author Henning Staib 095 */ 096public final class Socks5BytestreamManager extends Manager implements BytestreamManager { 097 098 /* 099 * create a new Socks5BytestreamManager and register a shutdown listener on every established 100 * connection 101 */ 102 static { 103 XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { 104 105 @Override 106 public void connectionCreated(final XMPPConnection connection) { 107 // create the manager for this connection 108 Socks5BytestreamManager.getBytestreamManager(connection); 109 } 110 111 }); 112 } 113 114 /* prefix used to generate session IDs */ 115 private static final String SESSION_ID_PREFIX = "js5_"; 116 117 /* stores one Socks5BytestreamManager for each XMPP connection */ 118 private static final Map<XMPPConnection, Socks5BytestreamManager> managers = new WeakHashMap<>(); 119 120 /* 121 * assigns a user to a listener that is informed if a bytestream request for this user is 122 * received 123 */ 124 private final Map<Jid, BytestreamListener> userListeners = new ConcurrentHashMap<>(); 125 126 /* 127 * list of listeners that respond to all bytestream requests if there are not user specific 128 * listeners for that request 129 */ 130 private final List<BytestreamListener> allRequestListeners = Collections.synchronizedList(new ArrayList<BytestreamListener>()); 131 132 /* listener that handles all incoming bytestream requests */ 133 private final InitiationListener initiationListener; 134 135 /* timeout to wait for the response to the SOCKS5 Bytestream initialization request */ 136 private int targetResponseTimeout = 10000; 137 138 /* timeout for connecting to the SOCKS5 proxy selected by the target */ 139 private int proxyConnectionTimeout = 10000; 140 141 /* blacklist of erroneous SOCKS5 proxies */ 142 private final Set<Jid> proxyBlacklist = Collections.synchronizedSet(new HashSet<Jid>()); 143 144 /* remember the last proxy that worked to prioritize it */ 145 private Jid lastWorkingProxy; 146 147 /* flag to enable/disable prioritization of last working proxy */ 148 private boolean proxyPrioritizationEnabled = true; 149 150 private boolean annouceLocalStreamHost = true; 151 152 /* 153 * list containing session IDs of SOCKS5 Bytestream initialization packets that should be 154 * ignored by the InitiationListener 155 */ 156 private final List<String> ignoredBytestreamRequests = Collections.synchronizedList(new ArrayList<String>()); 157 158 /** 159 * Returns the Socks5BytestreamManager to handle SOCKS5 Bytestreams for a given 160 * {@link XMPPConnection}. 161 * <p> 162 * If no manager exists a new is created and initialized. 163 * 164 * @param connection the XMPP connection or <code>null</code> if given connection is 165 * <code>null</code> 166 * @return the Socks5BytestreamManager for the given XMPP connection 167 */ 168 public static synchronized Socks5BytestreamManager getBytestreamManager(XMPPConnection connection) { 169 if (connection == null) { 170 return null; 171 } 172 Socks5BytestreamManager manager = managers.get(connection); 173 if (manager == null) { 174 manager = new Socks5BytestreamManager(connection); 175 managers.put(connection, manager); 176 } 177 return manager; 178 } 179 180 /** 181 * Private constructor. 182 * 183 * @param connection the XMPP connection 184 */ 185 private Socks5BytestreamManager(XMPPConnection connection) { 186 super(connection); 187 this.initiationListener = new InitiationListener(this); 188 activate(); 189 } 190 191 /** 192 * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request unless 193 * there is a user specific BytestreamListener registered. 194 * <p> 195 * If no listeners are registered all SOCKS5 Bytestream request are rejected with a 196 * <not-acceptable/> error. 197 * <p> 198 * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5 199 * bytestream requests sent in the context of <a 200 * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See 201 * {@link FileTransferManager}) 202 * 203 * @param listener the listener to register 204 */ 205 @Override 206 public void addIncomingBytestreamListener(BytestreamListener listener) { 207 this.allRequestListeners.add(listener); 208 } 209 210 /** 211 * Removes the given listener from the list of listeners for all incoming SOCKS5 Bytestream 212 * requests. 213 * 214 * @param listener the listener to remove 215 */ 216 @Override 217 public void removeIncomingBytestreamListener(BytestreamListener listener) { 218 this.allRequestListeners.remove(listener); 219 } 220 221 /** 222 * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request from the 223 * given user. 224 * <p> 225 * Use this method if you are awaiting an incoming SOCKS5 Bytestream request from a specific 226 * user. 227 * <p> 228 * If no listeners are registered all SOCKS5 Bytestream request are rejected with a 229 * <not-acceptable/> error. 230 * <p> 231 * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5 232 * bytestream requests sent in the context of <a 233 * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See 234 * {@link FileTransferManager}) 235 * 236 * @param listener the listener to register 237 * @param initiatorJID the JID of the user that wants to establish a SOCKS5 Bytestream 238 */ 239 @Override 240 public void addIncomingBytestreamListener(BytestreamListener listener, Jid initiatorJID) { 241 this.userListeners.put(initiatorJID, listener); 242 } 243 244 /** 245 * Removes the listener for the given user. 246 * 247 * @param initiatorJID the JID of the user the listener should be removed 248 */ 249 @Override 250 public void removeIncomingBytestreamListener(Jid initiatorJID) { 251 this.userListeners.remove(initiatorJID); 252 } 253 254 /** 255 * Use this method to ignore the next incoming SOCKS5 Bytestream request containing the given 256 * session ID. No listeners will be notified for this request and and no error will be returned 257 * to the initiator. 258 * <p> 259 * This method should be used if you are awaiting a SOCKS5 Bytestream request as a reply to 260 * another stanza (e.g. file transfer). 261 * 262 * @param sessionID to be ignored 263 */ 264 public void ignoreBytestreamRequestOnce(String sessionID) { 265 this.ignoredBytestreamRequests.add(sessionID); 266 } 267 268 /** 269 * Disables the SOCKS5 Bytestream manager by removing the SOCKS5 Bytestream feature from the 270 * service discovery, disabling the listener for SOCKS5 Bytestream initiation requests and 271 * resetting its internal state, which includes removing this instance from the managers map. 272 * <p> 273 * To re-enable the SOCKS5 Bytestream feature invoke {@link #getBytestreamManager(XMPPConnection)}. 274 * Using the file transfer API will automatically re-enable the SOCKS5 Bytestream feature. 275 */ 276 public synchronized void disableService() { 277 XMPPConnection connection = connection(); 278 // remove initiation packet listener 279 connection.unregisterIQRequestHandler(initiationListener); 280 281 // shutdown threads 282 this.initiationListener.shutdown(); 283 284 // clear listeners 285 this.allRequestListeners.clear(); 286 this.userListeners.clear(); 287 288 // reset internal state 289 this.lastWorkingProxy = null; 290 this.proxyBlacklist.clear(); 291 this.ignoredBytestreamRequests.clear(); 292 293 // remove manager from static managers map 294 managers.remove(connection); 295 296 // shutdown local SOCKS5 proxy if there are no more managers for other connections 297 if (managers.size() == 0) { 298 Socks5Proxy.getSocks5Proxy().stop(); 299 } 300 301 // remove feature from service discovery 302 ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); 303 304 // check if service discovery is not already disposed by connection shutdown 305 if (serviceDiscoveryManager != null) { 306 serviceDiscoveryManager.removeFeature(Bytestream.NAMESPACE); 307 } 308 309 } 310 311 /** 312 * Returns the timeout to wait for the response to the SOCKS5 Bytestream initialization request. 313 * Default is 10000ms. 314 * 315 * @return the timeout to wait for the response to the SOCKS5 Bytestream initialization request 316 */ 317 public int getTargetResponseTimeout() { 318 if (this.targetResponseTimeout <= 0) { 319 this.targetResponseTimeout = 10000; 320 } 321 return targetResponseTimeout; 322 } 323 324 /** 325 * Sets the timeout to wait for the response to the SOCKS5 Bytestream initialization request. 326 * Default is 10000ms. 327 * 328 * @param targetResponseTimeout the timeout to set 329 */ 330 public void setTargetResponseTimeout(int targetResponseTimeout) { 331 this.targetResponseTimeout = targetResponseTimeout; 332 } 333 334 /** 335 * Returns the timeout for connecting to the SOCKS5 proxy selected by the target. Default is 336 * 10000ms. 337 * 338 * @return the timeout for connecting to the SOCKS5 proxy selected by the target 339 */ 340 public int getProxyConnectionTimeout() { 341 if (this.proxyConnectionTimeout <= 0) { 342 this.proxyConnectionTimeout = 10000; 343 } 344 return proxyConnectionTimeout; 345 } 346 347 /** 348 * Sets the timeout for connecting to the SOCKS5 proxy selected by the target. Default is 349 * 10000ms. 350 * 351 * @param proxyConnectionTimeout the timeout to set 352 */ 353 public void setProxyConnectionTimeout(int proxyConnectionTimeout) { 354 this.proxyConnectionTimeout = proxyConnectionTimeout; 355 } 356 357 /** 358 * Returns if the prioritization of the last working SOCKS5 proxy on successive SOCKS5 359 * Bytestream connections is enabled. Default is <code>true</code>. 360 * 361 * @return <code>true</code> if prioritization is enabled, <code>false</code> otherwise 362 */ 363 public boolean isProxyPrioritizationEnabled() { 364 return proxyPrioritizationEnabled; 365 } 366 367 /** 368 * Enable/disable the prioritization of the last working SOCKS5 proxy on successive SOCKS5 369 * Bytestream connections. 370 * 371 * @param proxyPrioritizationEnabled enable/disable the prioritization of the last working 372 * SOCKS5 proxy 373 */ 374 public void setProxyPrioritizationEnabled(boolean proxyPrioritizationEnabled) { 375 this.proxyPrioritizationEnabled = proxyPrioritizationEnabled; 376 } 377 378 /** 379 * Returns if the bytestream manager will announce the local stream host(s), i.e. the local SOCKS5 proxy. 380 * <p> 381 * Local stream hosts will be announced if this option is enabled and at least one is running. 382 * </p> 383 * 384 * @return <code>true</code> if 385 * @since 4.4.0 386 */ 387 public boolean isAnnouncingLocalStreamHostEnabled() { 388 return annouceLocalStreamHost; 389 } 390 391 /** 392 * Set whether the bytestream manager will announce the local stream host(s), i.e. the local SOCKS5 proxy. 393 * 394 * @param announceLocalStreamHost TODO javadoc me please 395 * @see #isAnnouncingLocalStreamHostEnabled() 396 * @since 4.4.0 397 */ 398 public void setAnnounceLocalStreamHost(boolean announceLocalStreamHost) { 399 this.annouceLocalStreamHost = announceLocalStreamHost; 400 } 401 402 /** 403 * Establishes a SOCKS5 Bytestream with the given user and returns the Socket to send/receive 404 * data to/from the user. 405 * <p> 406 * Use this method to establish SOCKS5 Bytestreams to users accepting all incoming Socks5 407 * bytestream requests since this method doesn't provide a way to tell the user something about 408 * the data to be sent. 409 * <p> 410 * To establish a SOCKS5 Bytestream after negotiation the kind of data to be sent (e.g. file 411 * transfer) use {@link #establishSession(Jid, String)}. 412 * 413 * @param targetJID the JID of the user a SOCKS5 Bytestream should be established 414 * @return the Socket to send/receive data to/from the user 415 * @throws XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5 416 * Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies 417 * @throws IOException if the bytestream could not be established 418 * @throws InterruptedException if the current thread was interrupted while waiting 419 * @throws SmackException if there was no response from the server. 420 */ 421 @Override 422 public Socks5BytestreamSession establishSession(Jid targetJID) throws XMPPException, 423 IOException, InterruptedException, SmackException { 424 String sessionID = getNextSessionID(); 425 return establishSession(targetJID, sessionID); 426 } 427 428 /** 429 * Establishes a SOCKS5 Bytestream with the given user using the given session ID and returns 430 * the Socket to send/receive data to/from the user. 431 * 432 * @param targetJID the JID of the user a SOCKS5 Bytestream should be established 433 * @param sessionID the session ID for the SOCKS5 Bytestream request 434 * @return the Socket to send/receive data to/from the user 435 * @throws IOException if the bytestream could not be established 436 * @throws InterruptedException if the current thread was interrupted while waiting 437 * @throws XMPPException if an XMPP protocol error was received. 438 * @throws NotConnectedException if the XMPP connection is not connected. 439 * @throws NoResponseException if there was no response from the remote entity. 440 * @throws SmackMessageException if there was an error. 441 * @throws FeatureNotSupportedException if a requested feature is not supported by the remote entity. 442 */ 443 @Override 444 public Socks5BytestreamSession establishSession(Jid targetJID, String sessionID) 445 throws IOException, InterruptedException, XMPPException, NoResponseException, NotConnectedException, SmackMessageException, FeatureNotSupportedException { 446 XMPPConnection connection = connection(); 447 XMPPErrorException discoveryException = null; 448 // check if target supports SOCKS5 Bytestream 449 if (!supportsSocks5(targetJID)) { 450 throw new FeatureNotSupportedException("SOCKS5 Bytestream", targetJID); 451 } 452 453 List<Jid> proxies = new ArrayList<>(); 454 // determine SOCKS5 proxies from XMPP-server 455 try { 456 proxies.addAll(determineProxies()); 457 } catch (XMPPErrorException e) { 458 // don't abort here, just remember the exception thrown by determineProxies() 459 // determineStreamHostInfos() will at least add the local Socks5 proxy (if enabled) 460 discoveryException = e; 461 } 462 463 // determine address and port of each proxy 464 List<StreamHost> streamHosts = determineStreamHostInfos(proxies); 465 466 if (streamHosts.isEmpty()) { 467 if (discoveryException != null) { 468 throw discoveryException; 469 } else { 470 throw new SmackException.SmackMessageException("no SOCKS5 proxies available"); 471 } 472 } 473 474 // compute digest 475 String digest = Socks5Utils.createDigest(sessionID, connection.getUser(), targetJID); 476 477 // prioritize last working SOCKS5 proxy if exists 478 if (this.proxyPrioritizationEnabled && this.lastWorkingProxy != null) { 479 StreamHost selectedStreamHost = null; 480 for (StreamHost streamHost : streamHosts) { 481 if (streamHost.getJID().equals(this.lastWorkingProxy)) { 482 selectedStreamHost = streamHost; 483 break; 484 } 485 } 486 if (selectedStreamHost != null) { 487 streamHosts.remove(selectedStreamHost); 488 streamHosts.add(0, selectedStreamHost); 489 } 490 } 491 492 Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy(); 493 try { 494 // add transfer digest to local proxy to make transfer valid 495 socks5Proxy.addTransfer(digest); 496 497 // create initiation packet 498 Bytestream initiation = createBytestreamInitiation(sessionID, targetJID, streamHosts); 499 500 // send initiation packet 501 Stanza response = connection.createStanzaCollectorAndSend(initiation).nextResultOrThrow( 502 getTargetResponseTimeout()); 503 504 // extract used stream host from response 505 StreamHostUsed streamHostUsed = ((Bytestream) response).getUsedHost(); 506 StreamHost usedStreamHost = initiation.getStreamHost(streamHostUsed.getJID()); 507 508 if (usedStreamHost == null) { 509 throw new SmackException.SmackMessageException("Remote user responded with unknown host"); 510 } 511 512 // build SOCKS5 client 513 Socks5Client socks5Client = new Socks5ClientForInitiator(usedStreamHost, digest, 514 connection, sessionID, targetJID); 515 516 // establish connection to proxy 517 Socket socket = socks5Client.getSocket(getProxyConnectionTimeout()); 518 519 // remember last working SOCKS5 proxy to prioritize it for next request 520 this.lastWorkingProxy = usedStreamHost.getJID(); 521 522 // negotiation successful, return the output stream 523 return new Socks5BytestreamSession(socket, usedStreamHost.getJID().equals( 524 connection.getUser())); 525 } 526 catch (TimeoutException e) { 527 throw new IOException("Timeout while connecting to SOCKS5 proxy", e); 528 } 529 finally { 530 // remove transfer digest if output stream is returned or an exception 531 // occurred 532 socks5Proxy.removeTransfer(digest); 533 } 534 } 535 536 /** 537 * Returns <code>true</code> if the given target JID supports feature SOCKS5 Bytestream. 538 * 539 * @param targetJID the target JID 540 * @return <code>true</code> if the given target JID supports feature SOCKS5 Bytestream 541 * otherwise <code>false</code> 542 * @throws XMPPErrorException if there was an XMPP error returned. 543 * @throws NoResponseException if there was no response from the remote entity. 544 * @throws NotConnectedException if the XMPP connection is not connected. 545 * @throws InterruptedException if the calling thread was interrupted. 546 */ 547 private boolean supportsSocks5(Jid targetJID) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 548 return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(targetJID, Bytestream.NAMESPACE); 549 } 550 551 /** 552 * Returns a list of JIDs of SOCKS5 proxies by querying the XMPP server. The SOCKS5 proxies are 553 * in the same order as returned by the XMPP server. 554 * 555 * @return list of JIDs of SOCKS5 proxies 556 * @throws XMPPErrorException if there was an error querying the XMPP server for SOCKS5 proxies 557 * @throws NoResponseException if there was no response from the server. 558 * @throws NotConnectedException if the XMPP connection is not connected. 559 * @throws InterruptedException if the calling thread was interrupted. 560 */ 561 public List<Jid> determineProxies() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 562 XMPPConnection connection = connection(); 563 ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); 564 565 List<Jid> proxies = new ArrayList<>(); 566 567 // get all items from XMPP server 568 DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(connection.getXMPPServiceDomain()); 569 570 // query all items if they are SOCKS5 proxies 571 for (Item item : discoverItems.getItems()) { 572 // skip blacklisted servers 573 if (this.proxyBlacklist.contains(item.getEntityID())) { 574 continue; 575 } 576 577 DiscoverInfo proxyInfo; 578 try { 579 proxyInfo = serviceDiscoveryManager.discoverInfo(item.getEntityID()); 580 } 581 catch (NoResponseException | XMPPErrorException e) { 582 // blacklist erroneous server 583 proxyBlacklist.add(item.getEntityID()); 584 continue; 585 } 586 587 if (proxyInfo.hasIdentity("proxy", "bytestreams")) { 588 proxies.add(item.getEntityID()); 589 } else { 590 /* 591 * server is not a SOCKS5 proxy, blacklist server to skip next time a Socks5 592 * bytestream should be established 593 */ 594 this.proxyBlacklist.add(item.getEntityID()); 595 } 596 } 597 598 return proxies; 599 } 600 601 /** 602 * Returns a list of stream hosts containing the IP address an the port for the given list of 603 * SOCKS5 proxy JIDs. The order of the returned list is the same as the given list of JIDs 604 * excluding all SOCKS5 proxies who's network settings could not be determined. If a local 605 * SOCKS5 proxy is running it will be the first item in the list returned. 606 * 607 * @param proxies a list of SOCKS5 proxy JIDs 608 * @return a list of stream hosts containing the IP address an the port 609 */ 610 private List<StreamHost> determineStreamHostInfos(List<Jid> proxies) { 611 XMPPConnection connection = connection(); 612 List<StreamHost> streamHosts = new ArrayList<>(); 613 614 if (annouceLocalStreamHost) { 615 // add local proxy on first position if exists 616 List<StreamHost> localProxies = getLocalStreamHost(); 617 streamHosts.addAll(localProxies); 618 } 619 620 // query SOCKS5 proxies for network settings 621 for (Jid proxy : proxies) { 622 Bytestream streamHostRequest = createStreamHostRequest(proxy); 623 try { 624 Bytestream response = connection.sendIqRequestAndWaitForResponse( 625 streamHostRequest); 626 streamHosts.addAll(response.getStreamHosts()); 627 } 628 catch (Exception e) { 629 // blacklist erroneous proxies 630 this.proxyBlacklist.add(proxy); 631 } 632 } 633 634 return streamHosts; 635 } 636 637 /** 638 * Returns a IQ stanza to query a SOCKS5 proxy its network settings. 639 * 640 * @param proxy the proxy to query 641 * @return IQ stanza to query a SOCKS5 proxy its network settings 642 */ 643 private static Bytestream createStreamHostRequest(Jid proxy) { 644 Bytestream request = new Bytestream(); 645 request.setType(IQ.Type.get); 646 request.setTo(proxy); 647 return request; 648 } 649 650 /** 651 * Returns the stream host information of the local SOCKS5 proxy containing the IP address and 652 * the port. The returned list may be empty if the local SOCKS5 proxy is not running. 653 * 654 * @return the stream host information of the local SOCKS5 proxy 655 */ 656 public List<StreamHost> getLocalStreamHost() { 657 // Ensure that the local SOCKS5 proxy is running (if enabled). 658 Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy(); 659 660 List<StreamHost> streamHosts = new ArrayList<>(); 661 662 XMPPConnection connection = connection(); 663 EntityFullJid myJid = connection.getUser(); 664 665 // The default local address is often just 'the first address found in the 666 // list of addresses read from the OS' and this might mean an internal 667 // IP address that cannot reach external servers. So wherever possible 668 // use the same IP address being used to connect to the XMPP server 669 // because this local address has a better chance of being suitable. 670 InetAddress xmppLocalAddress = connection.getLocalAddress(); 671 if (xmppLocalAddress != null) { 672 socks5Proxy.replaceLocalAddresses(Collections.singletonList(xmppLocalAddress)); 673 } 674 675 for (Socks5Proxy socks5Server : Socks5Proxy.getRunningProxies()) { 676 List<InetAddress> addresses = socks5Server.getLocalAddresses(); 677 if (addresses.isEmpty()) { 678 continue; 679 } 680 681 final int port = socks5Server.getPort(); 682 for (InetAddress address : addresses) { 683 // Prevent loopback addresses from appearing as streamhost 684 if (address.isLoopbackAddress()) { 685 continue; 686 } 687 streamHosts.add(new StreamHost(myJid, address, port)); 688 } 689 } 690 691 return streamHosts; 692 } 693 694 /** 695 * Returns a SOCKS5 Bytestream initialization request stanza with the given session ID 696 * containing the given stream hosts for the given target JID. 697 * 698 * @param sessionID the session ID for the SOCKS5 Bytestream 699 * @param targetJID the target JID of SOCKS5 Bytestream request 700 * @param streamHosts a list of SOCKS5 proxies the target should connect to 701 * @return a SOCKS5 Bytestream initialization request packet 702 */ 703 private static Bytestream createBytestreamInitiation(String sessionID, Jid targetJID, 704 List<StreamHost> streamHosts) { 705 Bytestream initiation = new Bytestream(sessionID); 706 707 // add all stream hosts 708 for (StreamHost streamHost : streamHosts) { 709 initiation.addStreamHost(streamHost); 710 } 711 712 initiation.setType(IQ.Type.set); 713 initiation.setTo(targetJID); 714 715 return initiation; 716 } 717 718 /** 719 * Responses to the given packet's sender with an XMPP error that a SOCKS5 Bytestream is not 720 * accepted. 721 * <p> 722 * Specified in XEP-65 5.3.1 (Example 13) 723 * </p> 724 * 725 * @param packet Stanza that should be answered with a not-acceptable error 726 * @throws NotConnectedException if the XMPP connection is not connected. 727 * @throws InterruptedException if the calling thread was interrupted. 728 */ 729 void replyRejectPacket(IQ packet) throws NotConnectedException, InterruptedException { 730 StanzaError xmppError = StanzaError.getBuilder(StanzaError.Condition.not_acceptable).build(); 731 IQ errorIQ = IQ.createErrorResponse(packet, xmppError); 732 connection().sendStanza(errorIQ); 733 } 734 735 /** 736 * Activates the Socks5BytestreamManager by registering the SOCKS5 Bytestream initialization 737 * listener and enabling the SOCKS5 Bytestream feature. 738 */ 739 private void activate() { 740 // register bytestream initiation packet listener 741 connection().registerIQRequestHandler(initiationListener); 742 743 // enable SOCKS5 feature 744 enableService(); 745 } 746 747 /** 748 * Adds the SOCKS5 Bytestream feature to the service discovery. 749 */ 750 private void enableService() { 751 ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(connection()); 752 manager.addFeature(Bytestream.NAMESPACE); 753 } 754 755 /** 756 * Returns a new unique session ID. 757 * 758 * @return a new unique session ID 759 */ 760 private static String getNextSessionID() { 761 StringBuilder buffer = new StringBuilder(); 762 buffer.append(SESSION_ID_PREFIX); 763 buffer.append(StringUtils.secureOnlineAttackSafeRandomString()); 764 return buffer.toString(); 765 } 766 767 /** 768 * Returns the XMPP connection. 769 * 770 * @return the XMPP connection 771 */ 772 XMPPConnection getConnection() { 773 return connection(); 774 } 775 776 /** 777 * Returns the {@link BytestreamListener} that should be informed if a SOCKS5 Bytestream request 778 * from the given initiator JID is received. 779 * 780 * @param initiator the initiator's JID 781 * @return the listener 782 */ 783 BytestreamListener getUserListener(Jid initiator) { 784 return this.userListeners.get(initiator); 785 } 786 787 /** 788 * Returns a list of {@link BytestreamListener} that are informed if there are no listeners for 789 * a specific initiator. 790 * 791 * @return list of listeners 792 */ 793 List<BytestreamListener> getAllRequestListeners() { 794 return this.allRequestListeners; 795 } 796 797 /** 798 * Returns the list of session IDs that should be ignored by the InitiationListener 799 * 800 * @return list of session IDs 801 */ 802 List<String> getIgnoredBytestreamRequests() { 803 return ignoredBytestreamRequests; 804 } 805 806}