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