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