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