1 /** 2 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); 3 * you may not use this file except in compliance with the License. 4 * You may obtain a copy of the License at 5 * 6 * http://www.apache.org/licenses/LICENSE-2.0 7 * 8 * Unless required by applicable law or agreed to in writing, software 9 * distributed under the License is distributed on an "AS IS" BASIS, 10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 * See the License for the specific language governing permissions and 12 * limitations under the License. 13 */ 14 package org.jivesoftware.smackx.bytestreams.socks5; 15 16 import java.io.IOException; 17 import java.lang.ref.WeakReference; 18 import java.net.Socket; 19 import java.util.ArrayList; 20 import java.util.Collections; 21 import java.util.Iterator; 22 import java.util.LinkedList; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.Random; 26 import java.util.WeakHashMap; 27 import java.util.concurrent.ConcurrentHashMap; 28 import java.util.concurrent.TimeoutException; 29 30 import org.jivesoftware.smack.AbstractConnectionListener; 31 import org.jivesoftware.smack.Connection; 32 import org.jivesoftware.smack.ConnectionCreationListener; 33 import org.jivesoftware.smack.XMPPException; 34 import org.jivesoftware.smack.packet.IQ; 35 import org.jivesoftware.smack.packet.Packet; 36 import org.jivesoftware.smack.packet.XMPPError; 37 import org.jivesoftware.smack.util.SyncPacketSend; 38 import org.jivesoftware.smackx.ServiceDiscoveryManager; 39 import org.jivesoftware.smackx.bytestreams.BytestreamListener; 40 import org.jivesoftware.smackx.bytestreams.BytestreamManager; 41 import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; 42 import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; 43 import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHostUsed; 44 import org.jivesoftware.smackx.filetransfer.FileTransferManager; 45 import org.jivesoftware.smackx.packet.DiscoverInfo; 46 import org.jivesoftware.smackx.packet.DiscoverItems; 47 import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; 48 import org.jivesoftware.smackx.packet.DiscoverItems.Item; 49 50 /** 51 * The Socks5BytestreamManager class handles establishing SOCKS5 Bytestreams as specified in the <a 52 * href="http://xmpp.org/extensions/xep-0065.html">XEP-0065</a>. 53 * <p> 54 * A SOCKS5 Bytestream is negotiated partly over the XMPP XML stream and partly over a separate 55 * socket. The actual transfer though takes place over a separately created socket. 56 * <p> 57 * A SOCKS5 Bytestream generally has three parties, the initiator, the target, and the stream host. 58 * The stream host is a specialized SOCKS5 proxy setup on a server, or, the initiator can act as the 59 * stream host. 60 * <p> 61 * To establish a SOCKS5 Bytestream invoke the {@link #establishSession(String)} method. This will 62 * negotiate a SOCKS5 Bytestream with the given target JID and return a socket. 63 * <p> 64 * If a session ID for the SOCKS5 Bytestream was already negotiated (e.g. while negotiating a file 65 * transfer) invoke {@link #establishSession(String, String)}. 66 * <p> 67 * To handle incoming SOCKS5 Bytestream requests add an {@link Socks5BytestreamListener} to the 68 * manager. There are two ways to add this listener. If you want to be informed about incoming 69 * SOCKS5 Bytestreams from a specific user add the listener by invoking 70 * {@link #addIncomingBytestreamListener(BytestreamListener, String)}. If the listener should 71 * respond to all SOCKS5 Bytestream requests invoke 72 * {@link #addIncomingBytestreamListener(BytestreamListener)}. 73 * <p> 74 * Note that the registered {@link Socks5BytestreamListener} will NOT be notified on incoming Socks5 75 * bytestream requests sent in the context of <a 76 * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See 77 * {@link FileTransferManager}) 78 * <p> 79 * If no {@link Socks5BytestreamListener}s are registered, all incoming SOCKS5 Bytestream requests 80 * will be rejected by returning a <not-acceptable/> error to the initiator. 81 * 82 * @author Henning Staib 83 */ 84 public final class Socks5BytestreamManager implements BytestreamManager { 85 86 /* 87 * create a new Socks5BytestreamManager and register a shutdown listener on every established 88 * connection 89 */ 90 static { 91 Connection.addConnectionCreationListener(new ConnectionCreationListener() { 92 93 public void connectionCreated(final Connection connection) { 94 final Socks5BytestreamManager manager; 95 manager = Socks5BytestreamManager.getBytestreamManager(connection); 96 97 // register shutdown listener 98 connection.addConnectionListener(new AbstractConnectionListener() { 99 100 public void connectionClosed() { 101 manager.disableService(); 102 } 103 104 public void connectionClosedOnError(Exception e) { 105 manager.disableService(); 106 } 107 108 public void reconnectionSuccessful() { 109 managers.put(connection, manager); 110 } 111 112 }); 113 } 114 115 }); 116 } 117 118 /** 119 * The XMPP namespace of the SOCKS5 Bytestream 120 */ 121 public static final String NAMESPACE = "http://jabber.org/protocol/bytestreams"; 122 123 /* prefix used to generate session IDs */ 124 private static final String SESSION_ID_PREFIX = "js5_"; 125 126 /* random generator to create session IDs */ 127 private final static Random randomGenerator = new Random(); 128 129 /* stores one Socks5BytestreamManager for each XMPP connection */ 130 private final static Map<Connection, Socks5BytestreamManager> managers = new WeakHashMap<Connection, Socks5BytestreamManager>(); 131 132 /* XMPP connection */ 133 private final Connection connection; 134 135 /* 136 * assigns a user to a listener that is informed if a bytestream request for this user is 137 * received 138 */ 139 private final Map<String, BytestreamListener> userListeners = new ConcurrentHashMap<String, BytestreamListener>(); 140 141 /* 142 * list of listeners that respond to all bytestream requests if there are not user specific 143 * listeners for that request 144 */ 145 private final List<BytestreamListener> allRequestListeners = Collections.synchronizedList(new LinkedList<BytestreamListener>()); 146 147 /* listener that handles all incoming bytestream requests */ 148 private final InitiationListener initiationListener; 149 150 /* timeout to wait for the response to the SOCKS5 Bytestream initialization request */ 151 private int targetResponseTimeout = 10000; 152 153 /* timeout for connecting to the SOCKS5 proxy selected by the target */ 154 private int proxyConnectionTimeout = 10000; 155 156 /* blacklist of errornous SOCKS5 proxies */ 157 private final List<String> proxyBlacklist = Collections.synchronizedList(new LinkedList<String>()); 158 159 /* remember the last proxy that worked to prioritize it */ 160 private String lastWorkingProxy = null; 161 162 /* flag to enable/disable prioritization of last working proxy */ 163 private boolean proxyPrioritizationEnabled = true; 164 165 /* 166 * list containing session IDs of SOCKS5 Bytestream initialization packets that should be 167 * ignored by the InitiationListener 168 */ 169 private List<String> ignoredBytestreamRequests = Collections.synchronizedList(new LinkedList<String>()); 170 171 /** 172 * Returns the Socks5BytestreamManager to handle SOCKS5 Bytestreams for a given 173 * {@link Connection}. 174 * <p> 175 * If no manager exists a new is created and initialized. 176 * 177 * @param connection the XMPP connection or <code>null</code> if given connection is 178 * <code>null</code> 179 * @return the Socks5BytestreamManager for the given XMPP connection 180 */ 181 public static synchronized Socks5BytestreamManager getBytestreamManager(Connection connection) { 182 if (connection == null) { 183 return null; 184 } 185 Socks5BytestreamManager manager = managers.get(connection); 186 if (manager == null) { 187 manager = new Socks5BytestreamManager(connection); 188 managers.put(connection, manager); 189 manager.activate(); 190 } 191 return manager; 192 } 193 194 /** 195 * Private constructor. 196 * 197 * @param connection the XMPP connection 198 */ 199 private Socks5BytestreamManager(Connection connection) { 200 this.connection = connection; 201 this.initiationListener = new InitiationListener(this); 202 } 203 204 /** 205 * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request unless 206 * there is a user specific BytestreamListener registered. 207 * <p> 208 * If no listeners are registered all SOCKS5 Bytestream request are rejected with a 209 * <not-acceptable/> error. 210 * <p> 211 * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5 212 * bytestream requests sent in the context of <a 213 * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See 214 * {@link FileTransferManager}) 215 * 216 * @param listener the listener to register 217 */ 218 public void addIncomingBytestreamListener(BytestreamListener listener) { 219 this.allRequestListeners.add(listener); 220 } 221 222 /** 223 * Removes the given listener from the list of listeners for all incoming SOCKS5 Bytestream 224 * requests. 225 * 226 * @param listener the listener to remove 227 */ 228 public void removeIncomingBytestreamListener(BytestreamListener listener) { 229 this.allRequestListeners.remove(listener); 230 } 231 232 /** 233 * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request from the 234 * given user. 235 * <p> 236 * Use this method if you are awaiting an incoming SOCKS5 Bytestream request from a specific 237 * user. 238 * <p> 239 * If no listeners are registered all SOCKS5 Bytestream request are rejected with a 240 * <not-acceptable/> error. 241 * <p> 242 * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5 243 * bytestream requests sent in the context of <a 244 * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See 245 * {@link FileTransferManager}) 246 * 247 * @param listener the listener to register 248 * @param initiatorJID the JID of the user that wants to establish a SOCKS5 Bytestream 249 */ 250 public void addIncomingBytestreamListener(BytestreamListener listener, String initiatorJID) { 251 this.userListeners.put(initiatorJID, listener); 252 } 253 254 /** 255 * Removes the listener for the given user. 256 * 257 * @param initiatorJID the JID of the user the listener should be removed 258 */ 259 public void removeIncomingBytestreamListener(String initiatorJID) { 260 this.userListeners.remove(initiatorJID); 261 } 262 263 /** 264 * Use this method to ignore the next incoming SOCKS5 Bytestream request containing the given 265 * session ID. No listeners will be notified for this request and and no error will be returned 266 * to the initiator. 267 * <p> 268 * This method should be used if you are awaiting a SOCKS5 Bytestream request as a reply to 269 * another packet (e.g. file transfer). 270 * 271 * @param sessionID to be ignored 272 */ 273 public void ignoreBytestreamRequestOnce(String sessionID) { 274 this.ignoredBytestreamRequests.add(sessionID); 275 } 276 277 /** 278 * Disables the SOCKS5 Bytestream manager by removing the SOCKS5 Bytestream feature from the 279 * service discovery, disabling the listener for SOCKS5 Bytestream initiation requests and 280 * resetting its internal state. 281 * <p> 282 * To re-enable the SOCKS5 Bytestream feature invoke {@link #getBytestreamManager(Connection)}. 283 * Using the file transfer API will automatically re-enable the SOCKS5 Bytestream feature. 284 */ 285 public synchronized void disableService() { 286 287 // remove initiation packet listener 288 this.connection.removePacketListener(this.initiationListener); 289 290 // shutdown threads 291 this.initiationListener.shutdown(); 292 293 // clear listeners 294 this.allRequestListeners.clear(); 295 this.userListeners.clear(); 296 297 // reset internal state 298 this.lastWorkingProxy = null; 299 this.proxyBlacklist.clear(); 300 this.ignoredBytestreamRequests.clear(); 301 302 // remove manager from static managers map 303 managers.remove(this.connection); 304 305 // shutdown local SOCKS5 proxy if there are no more managers for other connections 306 if (managers.size() == 0) { 307 Socks5Proxy.getSocks5Proxy().stop(); 308 } 309 310 // remove feature from service discovery 311 ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); 312 313 // check if service discovery is not already disposed by connection shutdown 314 if (serviceDiscoveryManager != null) { 315 serviceDiscoveryManager.removeFeature(NAMESPACE); 316 } 317 318 } 319 320 /** 321 * Returns the timeout to wait for the response to the SOCKS5 Bytestream initialization request. 322 * Default is 10000ms. 323 * 324 * @return the timeout to wait for the response to the SOCKS5 Bytestream initialization request 325 */ 326 public int getTargetResponseTimeout() { 327 if (this.targetResponseTimeout <= 0) { 328 this.targetResponseTimeout = 10000; 329 } 330 return targetResponseTimeout; 331 } 332 333 /** 334 * Sets the timeout to wait for the response to the SOCKS5 Bytestream initialization request. 335 * Default is 10000ms. 336 * 337 * @param targetResponseTimeout the timeout to set 338 */ 339 public void setTargetResponseTimeout(int targetResponseTimeout) { 340 this.targetResponseTimeout = targetResponseTimeout; 341 } 342 343 /** 344 * Returns the timeout for connecting to the SOCKS5 proxy selected by the target. Default is 345 * 10000ms. 346 * 347 * @return the timeout for connecting to the SOCKS5 proxy selected by the target 348 */ 349 public int getProxyConnectionTimeout() { 350 if (this.proxyConnectionTimeout <= 0) { 351 this.proxyConnectionTimeout = 10000; 352 } 353 return proxyConnectionTimeout; 354 } 355 356 /** 357 * Sets the timeout for connecting to the SOCKS5 proxy selected by the target. Default is 358 * 10000ms. 359 * 360 * @param proxyConnectionTimeout the timeout to set 361 */ 362 public void setProxyConnectionTimeout(int proxyConnectionTimeout) { 363 this.proxyConnectionTimeout = proxyConnectionTimeout; 364 } 365 366 /** 367 * Returns if the prioritization of the last working SOCKS5 proxy on successive SOCKS5 368 * Bytestream connections is enabled. Default is <code>true</code>. 369 * 370 * @return <code>true</code> if prioritization is enabled, <code>false</code> otherwise 371 */ 372 public boolean isProxyPrioritizationEnabled() { 373 return proxyPrioritizationEnabled; 374 } 375 376 /** 377 * Enable/disable the prioritization of the last working SOCKS5 proxy on successive SOCKS5 378 * Bytestream connections. 379 * 380 * @param proxyPrioritizationEnabled enable/disable the prioritization of the last working 381 * SOCKS5 proxy 382 */ 383 public void setProxyPrioritizationEnabled(boolean proxyPrioritizationEnabled) { 384 this.proxyPrioritizationEnabled = proxyPrioritizationEnabled; 385 } 386 387 /** 388 * Establishes a SOCKS5 Bytestream with the given user and returns the Socket to send/receive 389 * data to/from the user. 390 * <p> 391 * Use this method to establish SOCKS5 Bytestreams to users accepting all incoming Socks5 392 * bytestream requests since this method doesn't provide a way to tell the user something about 393 * the data to be sent. 394 * <p> 395 * To establish a SOCKS5 Bytestream after negotiation the kind of data to be sent (e.g. file 396 * transfer) use {@link #establishSession(String, String)}. 397 * 398 * @param targetJID the JID of the user a SOCKS5 Bytestream should be established 399 * @return the Socket to send/receive data to/from the user 400 * @throws XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5 401 * Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies 402 * @throws IOException if the bytestream could not be established 403 * @throws InterruptedException if the current thread was interrupted while waiting 404 */ 405 public Socks5BytestreamSession establishSession(String targetJID) throws XMPPException, 406 IOException, InterruptedException { 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 XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5 419 * Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies 420 * @throws IOException if the bytestream could not be established 421 * @throws InterruptedException if the current thread was interrupted while waiting 422 */ 423 public Socks5BytestreamSession establishSession(String targetJID, String sessionID) 424 throws XMPPException, IOException, InterruptedException { 425 426 XMPPException discoveryException = null; 427 // check if target supports SOCKS5 Bytestream 428 if (!supportsSocks5(targetJID)) { 429 throw new XMPPException(targetJID + " doesn't support SOCKS5 Bytestream"); 430 } 431 432 List<String> proxies = new ArrayList<String>(); 433 // determine SOCKS5 proxies from XMPP-server 434 try { 435 proxies.addAll(determineProxies()); 436 } catch (XMPPException e) { 437 // don't abort here, just remember the exception thrown by determineProxies() 438 // determineStreamHostInfos() will at least add the local Socks5 proxy (if enabled) 439 discoveryException = e; 440 } 441 442 // determine address and port of each proxy 443 List<StreamHost> streamHosts = determineStreamHostInfos(proxies); 444 445 if (streamHosts.isEmpty()) { 446 throw discoveryException != null ? discoveryException : new XMPPException("no SOCKS5 proxies available"); 447 } 448 449 // compute digest 450 String digest = Socks5Utils.createDigest(sessionID, this.connection.getUser(), targetJID); 451 452 // prioritize last working SOCKS5 proxy if exists 453 if (this.proxyPrioritizationEnabled && this.lastWorkingProxy != null) { 454 StreamHost selectedStreamHost = null; 455 for (StreamHost streamHost : streamHosts) { 456 if (streamHost.getJID().equals(this.lastWorkingProxy)) { 457 selectedStreamHost = streamHost; 458 break; 459 } 460 } 461 if (selectedStreamHost != null) { 462 streamHosts.remove(selectedStreamHost); 463 streamHosts.add(0, selectedStreamHost); 464 } 465 466 } 467 468 Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy(); 469 try { 470 471 // add transfer digest to local proxy to make transfer valid 472 socks5Proxy.addTransfer(digest); 473 474 // create initiation packet 475 Bytestream initiation = createBytestreamInitiation(sessionID, targetJID, streamHosts); 476 477 // send initiation packet 478 Packet response = SyncPacketSend.getReply(this.connection, initiation, 479 getTargetResponseTimeout()); 480 481 // extract used stream host from response 482 StreamHostUsed streamHostUsed = ((Bytestream) response).getUsedHost(); 483 StreamHost usedStreamHost = initiation.getStreamHost(streamHostUsed.getJID()); 484 485 if (usedStreamHost == null) { 486 throw new XMPPException("Remote user responded with unknown host"); 487 } 488 489 // build SOCKS5 client 490 Socks5Client socks5Client = new Socks5ClientForInitiator(usedStreamHost, digest, 491 this.connection, sessionID, targetJID); 492 493 // establish connection to proxy 494 Socket socket = socks5Client.getSocket(getProxyConnectionTimeout()); 495 496 // remember last working SOCKS5 proxy to prioritize it for next request 497 this.lastWorkingProxy = usedStreamHost.getJID(); 498 499 // negotiation successful, return the output stream 500 return new Socks5BytestreamSession(socket, usedStreamHost.getJID().equals( 501 this.connection.getUser())); 502 503 } 504 catch (TimeoutException e) { 505 throw new IOException("Timeout while connecting to SOCKS5 proxy"); 506 } 507 finally { 508 509 // remove transfer digest if output stream is returned or an exception 510 // occurred 511 socks5Proxy.removeTransfer(digest); 512 513 } 514 } 515 516 /** 517 * Returns <code>true</code> if the given target JID supports feature SOCKS5 Bytestream. 518 * 519 * @param targetJID the target JID 520 * @return <code>true</code> if the given target JID supports feature SOCKS5 Bytestream 521 * otherwise <code>false</code> 522 * @throws XMPPException if there was an error querying target for supported features 523 */ 524 private boolean supportsSocks5(String targetJID) throws XMPPException { 525 ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); 526 DiscoverInfo discoverInfo = serviceDiscoveryManager.discoverInfo(targetJID); 527 return discoverInfo.containsFeature(NAMESPACE); 528 } 529 530 /** 531 * Returns a list of JIDs of SOCKS5 proxies by querying the XMPP server. The SOCKS5 proxies are 532 * in the same order as returned by the XMPP server. 533 * 534 * @return list of JIDs of SOCKS5 proxies 535 * @throws XMPPException if there was an error querying the XMPP server for SOCKS5 proxies 536 */ 537 private List<String> determineProxies() throws XMPPException { 538 ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); 539 540 List<String> proxies = new ArrayList<String>(); 541 542 // get all items form XMPP server 543 DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(this.connection.getServiceName()); 544 Iterator<Item> itemIterator = discoverItems.getItems(); 545 546 // query all items if they are SOCKS5 proxies 547 while (itemIterator.hasNext()) { 548 Item item = itemIterator.next(); 549 550 // skip blacklisted servers 551 if (this.proxyBlacklist.contains(item.getEntityID())) { 552 continue; 553 } 554 555 try { 556 DiscoverInfo proxyInfo; 557 proxyInfo = serviceDiscoveryManager.discoverInfo(item.getEntityID()); 558 Iterator<Identity> identities = proxyInfo.getIdentities(); 559 560 // item must have category "proxy" and type "bytestream" 561 while (identities.hasNext()) { 562 Identity identity = identities.next(); 563 564 if ("proxy".equalsIgnoreCase(identity.getCategory()) 565 && "bytestreams".equalsIgnoreCase(identity.getType())) { 566 proxies.add(item.getEntityID()); 567 break; 568 } 569 570 /* 571 * server is not a SOCKS5 proxy, blacklist server to skip next time a Socks5 572 * bytestream should be established 573 */ 574 this.proxyBlacklist.add(item.getEntityID()); 575 576 } 577 } 578 catch (XMPPException e) { 579 // blacklist errornous server 580 this.proxyBlacklist.add(item.getEntityID()); 581 } 582 } 583 584 return proxies; 585 } 586 587 /** 588 * Returns a list of stream hosts containing the IP address an the port for the given list of 589 * SOCKS5 proxy JIDs. The order of the returned list is the same as the given list of JIDs 590 * excluding all SOCKS5 proxies who's network settings could not be determined. If a local 591 * SOCKS5 proxy is running it will be the first item in the list returned. 592 * 593 * @param proxies a list of SOCKS5 proxy JIDs 594 * @return a list of stream hosts containing the IP address an the port 595 */ 596 private List<StreamHost> determineStreamHostInfos(List<String> proxies) { 597 List<StreamHost> streamHosts = new ArrayList<StreamHost>(); 598 599 // add local proxy on first position if exists 600 List<StreamHost> localProxies = getLocalStreamHost(); 601 if (localProxies != null) { 602 streamHosts.addAll(localProxies); 603 } 604 605 // query SOCKS5 proxies for network settings 606 for (String proxy : proxies) { 607 Bytestream streamHostRequest = createStreamHostRequest(proxy); 608 try { 609 Bytestream response = (Bytestream) SyncPacketSend.getReply(this.connection, 610 streamHostRequest); 611 streamHosts.addAll(response.getStreamHosts()); 612 } 613 catch (XMPPException e) { 614 // blacklist errornous proxies 615 this.proxyBlacklist.add(proxy); 616 } 617 } 618 619 return streamHosts; 620 } 621 622 /** 623 * Returns a IQ packet to query a SOCKS5 proxy its network settings. 624 * 625 * @param proxy the proxy to query 626 * @return IQ packet to query a SOCKS5 proxy its network settings 627 */ 628 private Bytestream createStreamHostRequest(String proxy) { 629 Bytestream request = new Bytestream(); 630 request.setType(IQ.Type.GET); 631 request.setTo(proxy); 632 return request; 633 } 634 635 /** 636 * Returns the stream host information of the local SOCKS5 proxy containing the IP address and 637 * the port or null if local SOCKS5 proxy is not running. 638 * 639 * @return the stream host information of the local SOCKS5 proxy or null if local SOCKS5 proxy 640 * is not running 641 */ 642 private List<StreamHost> getLocalStreamHost() { 643 644 // get local proxy singleton 645 Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy(); 646 647 if (socks5Server.isRunning()) { 648 List<String> addresses = socks5Server.getLocalAddresses(); 649 int port = socks5Server.getPort(); 650 651 if (addresses.size() >= 1) { 652 List<StreamHost> streamHosts = new ArrayList<StreamHost>(); 653 for (String address : addresses) { 654 StreamHost streamHost = new StreamHost(this.connection.getUser(), address); 655 streamHost.setPort(port); 656 streamHosts.add(streamHost); 657 } 658 return streamHosts; 659 } 660 661 } 662 663 // server is not running or local address could not be determined 664 return null; 665 } 666 667 /** 668 * Returns a SOCKS5 Bytestream initialization request packet with the given session ID 669 * containing the given stream hosts for the given target JID. 670 * 671 * @param sessionID the session ID for the SOCKS5 Bytestream 672 * @param targetJID the target JID of SOCKS5 Bytestream request 673 * @param streamHosts a list of SOCKS5 proxies the target should connect to 674 * @return a SOCKS5 Bytestream initialization request packet 675 */ 676 private Bytestream createBytestreamInitiation(String sessionID, String targetJID, 677 List<StreamHost> streamHosts) { 678 Bytestream initiation = new Bytestream(sessionID); 679 680 // add all stream hosts 681 for (StreamHost streamHost : streamHosts) { 682 initiation.addStreamHost(streamHost); 683 } 684 685 initiation.setType(IQ.Type.SET); 686 initiation.setTo(targetJID); 687 688 return initiation; 689 } 690 691 /** 692 * Responses to the given packet's sender with a XMPP error that a SOCKS5 Bytestream is not 693 * accepted. 694 * 695 * @param packet Packet that should be answered with a not-acceptable error 696 */ 697 protected void replyRejectPacket(IQ packet) { 698 XMPPError xmppError = new XMPPError(XMPPError.Condition.no_acceptable); 699 IQ errorIQ = IQ.createErrorResponse(packet, xmppError); 700 this.connection.sendPacket(errorIQ); 701 } 702 703 /** 704 * Activates the Socks5BytestreamManager by registering the SOCKS5 Bytestream initialization 705 * listener and enabling the SOCKS5 Bytestream feature. 706 */ 707 private void activate() { 708 // register bytestream initiation packet listener 709 this.connection.addPacketListener(this.initiationListener, 710 this.initiationListener.getFilter()); 711 712 // enable SOCKS5 feature 713 enableService(); 714 } 715 716 /** 717 * Adds the SOCKS5 Bytestream feature to the service discovery. 718 */ 719 private void enableService() { 720 ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(this.connection); 721 if (!manager.includesFeature(NAMESPACE)) { 722 manager.addFeature(NAMESPACE); 723 } 724 } 725 726 /** 727 * Returns a new unique session ID. 728 * 729 * @return a new unique session ID 730 */ 731 private String getNextSessionID() { 732 StringBuilder buffer = new StringBuilder(); 733 buffer.append(SESSION_ID_PREFIX); 734 buffer.append(Math.abs(randomGenerator.nextLong())); 735 return buffer.toString(); 736 } 737 738 /** 739 * Returns the XMPP connection. 740 * 741 * @return the XMPP connection 742 */ 743 protected Connection getConnection() { 744 return this.connection; 745 } 746 747 /** 748 * Returns the {@link BytestreamListener} that should be informed if a SOCKS5 Bytestream request 749 * from the given initiator JID is received. 750 * 751 * @param initiator the initiator's JID 752 * @return the listener 753 */ 754 protected BytestreamListener getUserListener(String initiator) { 755 return this.userListeners.get(initiator); 756 } 757 758 /** 759 * Returns a list of {@link BytestreamListener} that are informed if there are no listeners for 760 * a specific initiator. 761 * 762 * @return list of listeners 763 */ 764 protected List<BytestreamListener> getAllRequestListeners() { 765 return this.allRequestListeners; 766 } 767 768 /** 769 * Returns the list of session IDs that should be ignored by the InitialtionListener 770 * 771 * @return list of session IDs 772 */ 773 protected List<String> getIgnoredBytestreamRequests() { 774 return ignoredBytestreamRequests; 775 } 776 777 } 778