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