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 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 NoResponseException 415 * @throws SmackException if the target does not support SOCKS5. 416 * @throws XMPPException 417 */ 418 @Override 419 public Socks5BytestreamSession establishSession(Jid targetJID, String sessionID) 420 throws IOException, InterruptedException, NoResponseException, SmackException, XMPPException { 421 XMPPConnection connection = connection(); 422 XMPPErrorException discoveryException = null; 423 // check if target supports SOCKS5 Bytestream 424 if (!supportsSocks5(targetJID)) { 425 throw new FeatureNotSupportedException("SOCKS5 Bytestream", targetJID); 426 } 427 428 List<Jid> proxies = new ArrayList<>(); 429 // determine SOCKS5 proxies from XMPP-server 430 try { 431 proxies.addAll(determineProxies()); 432 } catch (XMPPErrorException e) { 433 // don't abort here, just remember the exception thrown by determineProxies() 434 // determineStreamHostInfos() will at least add the local Socks5 proxy (if enabled) 435 discoveryException = e; 436 } 437 438 // determine address and port of each proxy 439 List<StreamHost> streamHosts = determineStreamHostInfos(proxies); 440 441 if (streamHosts.isEmpty()) { 442 if (discoveryException != null) { 443 throw discoveryException; 444 } else { 445 throw new SmackException("no SOCKS5 proxies available"); 446 } 447 } 448 449 // compute digest 450 String digest = Socks5Utils.createDigest(sessionID, 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 Stanza response = connection.createStanzaCollectorAndSend(initiation).nextResultOrThrow( 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 SmackException("Remote user responded with unknown host"); 487 } 488 489 // build SOCKS5 client 490 Socks5Client socks5Client = new Socks5ClientForInitiator(usedStreamHost, digest, 491 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 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 XMPPErrorException 523 * @throws NoResponseException 524 * @throws NotConnectedException 525 * @throws InterruptedException 526 */ 527 private boolean supportsSocks5(Jid targetJID) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 528 return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(targetJID, Bytestream.NAMESPACE); 529 } 530 531 /** 532 * Returns a list of JIDs of SOCKS5 proxies by querying the XMPP server. The SOCKS5 proxies are 533 * in the same order as returned by the XMPP server. 534 * 535 * @return list of JIDs of SOCKS5 proxies 536 * @throws XMPPErrorException if there was an error querying the XMPP server for SOCKS5 proxies 537 * @throws NoResponseException if there was no response from the server. 538 * @throws NotConnectedException 539 * @throws InterruptedException 540 */ 541 private List<Jid> determineProxies() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 542 XMPPConnection connection = connection(); 543 ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); 544 545 List<Jid> proxies = new ArrayList<>(); 546 547 // get all items from XMPP server 548 DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(connection.getXMPPServiceDomain()); 549 550 // query all items if they are SOCKS5 proxies 551 for (Item item : discoverItems.getItems()) { 552 // skip blacklisted servers 553 if (this.proxyBlacklist.contains(item.getEntityID())) { 554 continue; 555 } 556 557 DiscoverInfo proxyInfo; 558 try { 559 proxyInfo = serviceDiscoveryManager.discoverInfo(item.getEntityID()); 560 } 561 catch (NoResponseException | XMPPErrorException e) { 562 // blacklist errornous server 563 proxyBlacklist.add(item.getEntityID()); 564 continue; 565 } 566 567 if (proxyInfo.hasIdentity("proxy", "bytestreams")) { 568 proxies.add(item.getEntityID()); 569 } else { 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 return proxies; 579 } 580 581 /** 582 * Returns a list of stream hosts containing the IP address an the port for the given list of 583 * SOCKS5 proxy JIDs. The order of the returned list is the same as the given list of JIDs 584 * excluding all SOCKS5 proxies who's network settings could not be determined. If a local 585 * SOCKS5 proxy is running it will be the first item in the list returned. 586 * 587 * @param proxies a list of SOCKS5 proxy JIDs 588 * @return a list of stream hosts containing the IP address an the port 589 */ 590 private List<StreamHost> determineStreamHostInfos(List<Jid> proxies) { 591 XMPPConnection connection = connection(); 592 List<StreamHost> streamHosts = new ArrayList<StreamHost>(); 593 594 // add local proxy on first position if exists 595 List<StreamHost> localProxies = getLocalStreamHost(); 596 if (localProxies != null) { 597 streamHosts.addAll(localProxies); 598 } 599 600 // query SOCKS5 proxies for network settings 601 for (Jid proxy : proxies) { 602 Bytestream streamHostRequest = createStreamHostRequest(proxy); 603 try { 604 Bytestream response = (Bytestream) connection.createStanzaCollectorAndSend( 605 streamHostRequest).nextResultOrThrow(); 606 streamHosts.addAll(response.getStreamHosts()); 607 } 608 catch (Exception e) { 609 // blacklist errornous proxies 610 this.proxyBlacklist.add(proxy); 611 } 612 } 613 614 return streamHosts; 615 } 616 617 /** 618 * Returns a IQ stanza(/packet) to query a SOCKS5 proxy its network settings. 619 * 620 * @param proxy the proxy to query 621 * @return IQ stanza(/packet) to query a SOCKS5 proxy its network settings 622 */ 623 private static Bytestream createStreamHostRequest(Jid proxy) { 624 Bytestream request = new Bytestream(); 625 request.setType(IQ.Type.get); 626 request.setTo(proxy); 627 return request; 628 } 629 630 /** 631 * Returns the stream host information of the local SOCKS5 proxy containing the IP address and 632 * the port or null if local SOCKS5 proxy is not running. 633 * 634 * @return the stream host information of the local SOCKS5 proxy or null if local SOCKS5 proxy 635 * is not running 636 */ 637 private List<StreamHost> getLocalStreamHost() { 638 XMPPConnection connection = connection(); 639 // get local proxy singleton 640 Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy(); 641 642 if (!socks5Server.isRunning()) { 643 // server is not running 644 return null; 645 } 646 List<String> addresses = socks5Server.getLocalAddresses(); 647 if (addresses.isEmpty()) { 648 // local address could not be determined 649 return null; 650 } 651 final int port = socks5Server.getPort(); 652 653 List<StreamHost> streamHosts = new ArrayList<StreamHost>(); 654 outerloop: for (String address : addresses) { 655 // Prevent loopback addresses from appearing as streamhost 656 final String[] loopbackAddresses = { "127.0.0.1", "0:0:0:0:0:0:0:1", "::1" }; 657 for (String loopbackAddress : loopbackAddresses) { 658 // Use 'startsWith' here since IPv6 addresses may have scope ID, 659 // ie. the part after the '%' sign. 660 if (address.startsWith(loopbackAddress)) { 661 continue outerloop; 662 } 663 } 664 streamHosts.add(new StreamHost(connection.getUser(), address, port)); 665 } 666 return streamHosts; 667 } 668 669 /** 670 * Returns a SOCKS5 Bytestream initialization request stanza(/packet) with the given session ID 671 * containing the given stream hosts for the given target JID. 672 * 673 * @param sessionID the session ID for the SOCKS5 Bytestream 674 * @param targetJID the target JID of SOCKS5 Bytestream request 675 * @param streamHosts a list of SOCKS5 proxies the target should connect to 676 * @return a SOCKS5 Bytestream initialization request packet 677 */ 678 private static Bytestream createBytestreamInitiation(String sessionID, Jid targetJID, 679 List<StreamHost> streamHosts) { 680 Bytestream initiation = new Bytestream(sessionID); 681 682 // add all stream hosts 683 for (StreamHost streamHost : streamHosts) { 684 initiation.addStreamHost(streamHost); 685 } 686 687 initiation.setType(IQ.Type.set); 688 initiation.setTo(targetJID); 689 690 return initiation; 691 } 692 693 /** 694 * Responses to the given packet's sender with an XMPP error that a SOCKS5 Bytestream is not 695 * accepted. 696 * <p> 697 * Specified in XEP-65 5.3.1 (Example 13) 698 * </p> 699 * 700 * @param packet Stanza(/Packet) that should be answered with a not-acceptable error 701 * @throws NotConnectedException 702 * @throws InterruptedException 703 */ 704 protected void replyRejectPacket(IQ packet) throws NotConnectedException, InterruptedException { 705 XMPPError.Builder xmppError = XMPPError.getBuilder(XMPPError.Condition.not_acceptable); 706 IQ errorIQ = IQ.createErrorResponse(packet, xmppError); 707 connection().sendStanza(errorIQ); 708 } 709 710 /** 711 * Activates the Socks5BytestreamManager by registering the SOCKS5 Bytestream initialization 712 * listener and enabling the SOCKS5 Bytestream feature. 713 */ 714 private void activate() { 715 // register bytestream initiation packet listener 716 connection().registerIQRequestHandler(initiationListener); 717 718 // enable SOCKS5 feature 719 enableService(); 720 } 721 722 /** 723 * Adds the SOCKS5 Bytestream feature to the service discovery. 724 */ 725 private void enableService() { 726 ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(connection()); 727 manager.addFeature(Bytestream.NAMESPACE); 728 } 729 730 /** 731 * Returns a new unique session ID. 732 * 733 * @return a new unique session ID 734 */ 735 private static String getNextSessionID() { 736 StringBuilder buffer = new StringBuilder(); 737 buffer.append(SESSION_ID_PREFIX); 738 buffer.append(Math.abs(randomGenerator.nextLong())); 739 return buffer.toString(); 740 } 741 742 /** 743 * Returns the XMPP connection. 744 * 745 * @return the XMPP connection 746 */ 747 protected XMPPConnection getConnection() { 748 return connection(); 749 } 750 751 /** 752 * Returns the {@link BytestreamListener} that should be informed if a SOCKS5 Bytestream request 753 * from the given initiator JID is received. 754 * 755 * @param initiator the initiator's JID 756 * @return the listener 757 */ 758 protected BytestreamListener getUserListener(Jid initiator) { 759 return this.userListeners.get(initiator); 760 } 761 762 /** 763 * Returns a list of {@link BytestreamListener} that are informed if there are no listeners for 764 * a specific initiator. 765 * 766 * @return list of listeners 767 */ 768 protected List<BytestreamListener> getAllRequestListeners() { 769 return this.allRequestListeners; 770 } 771 772 /** 773 * Returns the list of session IDs that should be ignored by the InitialtionListener 774 * 775 * @return list of session IDs 776 */ 777 protected List<String> getIgnoredBytestreamRequests() { 778 return ignoredBytestreamRequests; 779 } 780 781}