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