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