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