Socks5BytestreamManager.java

  1. /**
  2.  *
  3.  * Copyright the original author or authors
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.bytestreams.socks5;

  18. import java.io.IOException;
  19. import java.net.Socket;
  20. import java.util.ArrayList;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.HashSet;
  24. import java.util.LinkedList;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Random;
  28. import java.util.Set;
  29. import java.util.concurrent.ConcurrentHashMap;
  30. import java.util.concurrent.TimeoutException;

  31. import org.jivesoftware.smack.AbstractConnectionClosedListener;
  32. import org.jivesoftware.smack.SmackException;
  33. import org.jivesoftware.smack.SmackException.NoResponseException;
  34. import org.jivesoftware.smack.SmackException.FeatureNotSupportedException;
  35. import org.jivesoftware.smack.SmackException.NotConnectedException;
  36. import org.jivesoftware.smack.XMPPConnection;
  37. import org.jivesoftware.smack.ConnectionCreationListener;
  38. import org.jivesoftware.smack.XMPPConnectionRegistry;
  39. import org.jivesoftware.smack.XMPPException;
  40. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  41. import org.jivesoftware.smack.packet.IQ;
  42. import org.jivesoftware.smack.packet.Stanza;
  43. import org.jivesoftware.smack.packet.XMPPError;
  44. import org.jivesoftware.smackx.bytestreams.BytestreamListener;
  45. import org.jivesoftware.smackx.bytestreams.BytestreamManager;
  46. import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
  47. import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost;
  48. import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHostUsed;
  49. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  50. import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
  51. import org.jivesoftware.smackx.disco.packet.DiscoverItems;
  52. import org.jivesoftware.smackx.disco.packet.DiscoverItems.Item;
  53. import org.jivesoftware.smackx.filetransfer.FileTransferManager;
  54. import org.jxmpp.jid.Jid;

  55. /**
  56.  * The Socks5BytestreamManager class handles establishing SOCKS5 Bytestreams as specified in the <a
  57.  * href="http://xmpp.org/extensions/xep-0065.html">XEP-0065</a>.
  58.  * <p>
  59.  * A SOCKS5 Bytestream is negotiated partly over the XMPP XML stream and partly over a separate
  60.  * socket. The actual transfer though takes place over a separately created socket.
  61.  * <p>
  62.  * A SOCKS5 Bytestream generally has three parties, the initiator, the target, and the stream host.
  63.  * The stream host is a specialized SOCKS5 proxy setup on a server, or, the initiator can act as the
  64.  * stream host.
  65.  * <p>
  66.  * To establish a SOCKS5 Bytestream invoke the {@link #establishSession(Jid)} method. This will
  67.  * negotiate a SOCKS5 Bytestream with the given target JID and return a socket.
  68.  * <p>
  69.  * If a session ID for the SOCKS5 Bytestream was already negotiated (e.g. while negotiating a file
  70.  * transfer) invoke {@link #establishSession(Jid, String)}.
  71.  * <p>
  72.  * To handle incoming SOCKS5 Bytestream requests add an {@link Socks5BytestreamListener} to the
  73.  * manager. There are two ways to add this listener. If you want to be informed about incoming
  74.  * SOCKS5 Bytestreams from a specific user add the listener by invoking
  75.  * {@link #addIncomingBytestreamListener(BytestreamListener, Jid)}. If the listener should
  76.  * respond to all SOCKS5 Bytestream requests invoke
  77.  * {@link #addIncomingBytestreamListener(BytestreamListener)}.
  78.  * <p>
  79.  * Note that the registered {@link Socks5BytestreamListener} will NOT be notified on incoming Socks5
  80.  * bytestream requests sent in the context of <a
  81.  * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See
  82.  * {@link FileTransferManager})
  83.  * <p>
  84.  * If no {@link Socks5BytestreamListener}s are registered, all incoming SOCKS5 Bytestream requests
  85.  * will be rejected by returning a &lt;not-acceptable/&gt; error to the initiator.
  86.  *
  87.  * @author Henning Staib
  88.  */
  89. public final class Socks5BytestreamManager implements BytestreamManager {

  90.     /*
  91.      * create a new Socks5BytestreamManager and register a shutdown listener on every established
  92.      * connection
  93.      */
  94.     static {
  95.         XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {

  96.             public void connectionCreated(final XMPPConnection connection) {
  97.                 // create the manager for this connection
  98.                 Socks5BytestreamManager.getBytestreamManager(connection);

  99.                 // register shutdown listener
  100.                 connection.addConnectionListener(new AbstractConnectionClosedListener() {

  101.                     @Override
  102.                     public void connectionTerminated() {
  103.                         Socks5BytestreamManager.getBytestreamManager(connection).disableService();
  104.                     }

  105.                     @Override
  106.                     public void reconnectionSuccessful() {
  107.                         // re-create the manager for this connection
  108.                         Socks5BytestreamManager.getBytestreamManager(connection);
  109.                     }

  110.                 });
  111.             }

  112.         });
  113.     }

  114.     /* prefix used to generate session IDs */
  115.     private static final String SESSION_ID_PREFIX = "js5_";

  116.     /* random generator to create session IDs */
  117.     private final static Random randomGenerator = new Random();

  118.     /* stores one Socks5BytestreamManager for each XMPP connection */
  119.     private final static Map<XMPPConnection, Socks5BytestreamManager> managers = new HashMap<XMPPConnection, Socks5BytestreamManager>();

  120.     /* XMPP connection */
  121.     private final XMPPConnection connection;

  122.     /*
  123.      * assigns a user to a listener that is informed if a bytestream request for this user is
  124.      * received
  125.      */
  126.     private final Map<Jid, BytestreamListener> userListeners = new ConcurrentHashMap<>();

  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.     /* listener that handles all incoming bytestream requests */
  133.     private final InitiationListener initiationListener;

  134.     /* timeout to wait for the response to the SOCKS5 Bytestream initialization request */
  135.     private int targetResponseTimeout = 10000;

  136.     /* timeout for connecting to the SOCKS5 proxy selected by the target */
  137.     private int proxyConnectionTimeout = 10000;

  138.     /* blacklist of errornous SOCKS5 proxies */
  139.     private final Set<Jid> proxyBlacklist = Collections.synchronizedSet(new HashSet<Jid>());

  140.     /* remember the last proxy that worked to prioritize it */
  141.     private Jid lastWorkingProxy;

  142.     /* flag to enable/disable prioritization of last working proxy */
  143.     private boolean proxyPrioritizationEnabled = true;

  144.     /*
  145.      * list containing session IDs of SOCKS5 Bytestream initialization packets that should be
  146.      * ignored by the InitiationListener
  147.      */
  148.     private List<String> ignoredBytestreamRequests = Collections.synchronizedList(new LinkedList<String>());

  149.     /**
  150.      * Returns the Socks5BytestreamManager to handle SOCKS5 Bytestreams for a given
  151.      * {@link XMPPConnection}.
  152.      * <p>
  153.      * If no manager exists a new is created and initialized.
  154.      *
  155.      * @param connection the XMPP connection or <code>null</code> if given connection is
  156.      *        <code>null</code>
  157.      * @return the Socks5BytestreamManager for the given XMPP connection
  158.      */
  159.     public static synchronized Socks5BytestreamManager getBytestreamManager(XMPPConnection connection) {
  160.         if (connection == null) {
  161.             return null;
  162.         }
  163.         Socks5BytestreamManager manager = managers.get(connection);
  164.         if (manager == null) {
  165.             manager = new Socks5BytestreamManager(connection);
  166.             managers.put(connection, manager);
  167.             manager.activate();
  168.         }
  169.         return manager;
  170.     }

  171.     /**
  172.      * Private constructor.
  173.      *
  174.      * @param connection the XMPP connection
  175.      */
  176.     private Socks5BytestreamManager(XMPPConnection connection) {
  177.         this.connection = connection;
  178.         this.initiationListener = new InitiationListener(this);
  179.     }

  180.     /**
  181.      * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request unless
  182.      * there is a user specific BytestreamListener registered.
  183.      * <p>
  184.      * If no listeners are registered all SOCKS5 Bytestream request are rejected with a
  185.      * &lt;not-acceptable/&gt; error.
  186.      * <p>
  187.      * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5
  188.      * bytestream requests sent in the context of <a
  189.      * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See
  190.      * {@link FileTransferManager})
  191.      *
  192.      * @param listener the listener to register
  193.      */
  194.     public void addIncomingBytestreamListener(BytestreamListener listener) {
  195.         this.allRequestListeners.add(listener);
  196.     }

  197.     /**
  198.      * Removes the given listener from the list of listeners for all incoming SOCKS5 Bytestream
  199.      * requests.
  200.      *
  201.      * @param listener the listener to remove
  202.      */
  203.     public void removeIncomingBytestreamListener(BytestreamListener listener) {
  204.         this.allRequestListeners.remove(listener);
  205.     }

  206.     /**
  207.      * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request from the
  208.      * given user.
  209.      * <p>
  210.      * Use this method if you are awaiting an incoming SOCKS5 Bytestream request from a specific
  211.      * user.
  212.      * <p>
  213.      * If no listeners are registered all SOCKS5 Bytestream request are rejected with a
  214.      * &lt;not-acceptable/&gt; error.
  215.      * <p>
  216.      * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5
  217.      * bytestream requests sent in the context of <a
  218.      * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See
  219.      * {@link FileTransferManager})
  220.      *
  221.      * @param listener the listener to register
  222.      * @param initiatorJID the JID of the user that wants to establish a SOCKS5 Bytestream
  223.      */
  224.     public void addIncomingBytestreamListener(BytestreamListener listener, Jid initiatorJID) {
  225.         this.userListeners.put(initiatorJID, listener);
  226.     }

  227.     /**
  228.      * Removes the listener for the given user.
  229.      *
  230.      * @param initiatorJID the JID of the user the listener should be removed
  231.      */
  232.     public void removeIncomingBytestreamListener(String initiatorJID) {
  233.         this.userListeners.remove(initiatorJID);
  234.     }

  235.     /**
  236.      * Use this method to ignore the next incoming SOCKS5 Bytestream request containing the given
  237.      * session ID. No listeners will be notified for this request and and no error will be returned
  238.      * to the initiator.
  239.      * <p>
  240.      * This method should be used if you are awaiting a SOCKS5 Bytestream request as a reply to
  241.      * another packet (e.g. file transfer).
  242.      *
  243.      * @param sessionID to be ignored
  244.      */
  245.     public void ignoreBytestreamRequestOnce(String sessionID) {
  246.         this.ignoredBytestreamRequests.add(sessionID);
  247.     }

  248.     /**
  249.      * Disables the SOCKS5 Bytestream manager by removing the SOCKS5 Bytestream feature from the
  250.      * service discovery, disabling the listener for SOCKS5 Bytestream initiation requests and
  251.      * resetting its internal state, which includes removing this instance from the managers map.
  252.      * <p>
  253.      * To re-enable the SOCKS5 Bytestream feature invoke {@link #getBytestreamManager(XMPPConnection)}.
  254.      * Using the file transfer API will automatically re-enable the SOCKS5 Bytestream feature.
  255.      */
  256.     public synchronized void disableService() {

  257.         // remove initiation packet listener
  258.         connection.unregisterIQRequestHandler(initiationListener);

  259.         // shutdown threads
  260.         this.initiationListener.shutdown();

  261.         // clear listeners
  262.         this.allRequestListeners.clear();
  263.         this.userListeners.clear();

  264.         // reset internal state
  265.         this.lastWorkingProxy = null;
  266.         this.proxyBlacklist.clear();
  267.         this.ignoredBytestreamRequests.clear();

  268.         // remove manager from static managers map
  269.         managers.remove(this.connection);

  270.         // shutdown local SOCKS5 proxy if there are no more managers for other connections
  271.         if (managers.size() == 0) {
  272.             Socks5Proxy.getSocks5Proxy().stop();
  273.         }

  274.         // remove feature from service discovery
  275.         ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection);

  276.         // check if service discovery is not already disposed by connection shutdown
  277.         if (serviceDiscoveryManager != null) {
  278.             serviceDiscoveryManager.removeFeature(Bytestream.NAMESPACE);
  279.         }

  280.     }

  281.     /**
  282.      * Returns the timeout to wait for the response to the SOCKS5 Bytestream initialization request.
  283.      * Default is 10000ms.
  284.      *
  285.      * @return the timeout to wait for the response to the SOCKS5 Bytestream initialization request
  286.      */
  287.     public int getTargetResponseTimeout() {
  288.         if (this.targetResponseTimeout <= 0) {
  289.             this.targetResponseTimeout = 10000;
  290.         }
  291.         return targetResponseTimeout;
  292.     }

  293.     /**
  294.      * Sets the timeout to wait for the response to the SOCKS5 Bytestream initialization request.
  295.      * Default is 10000ms.
  296.      *
  297.      * @param targetResponseTimeout the timeout to set
  298.      */
  299.     public void setTargetResponseTimeout(int targetResponseTimeout) {
  300.         this.targetResponseTimeout = targetResponseTimeout;
  301.     }

  302.     /**
  303.      * Returns the timeout for connecting to the SOCKS5 proxy selected by the target. Default is
  304.      * 10000ms.
  305.      *
  306.      * @return the timeout for connecting to the SOCKS5 proxy selected by the target
  307.      */
  308.     public int getProxyConnectionTimeout() {
  309.         if (this.proxyConnectionTimeout <= 0) {
  310.             this.proxyConnectionTimeout = 10000;
  311.         }
  312.         return proxyConnectionTimeout;
  313.     }

  314.     /**
  315.      * Sets the timeout for connecting to the SOCKS5 proxy selected by the target. Default is
  316.      * 10000ms.
  317.      *
  318.      * @param proxyConnectionTimeout the timeout to set
  319.      */
  320.     public void setProxyConnectionTimeout(int proxyConnectionTimeout) {
  321.         this.proxyConnectionTimeout = proxyConnectionTimeout;
  322.     }

  323.     /**
  324.      * Returns if the prioritization of the last working SOCKS5 proxy on successive SOCKS5
  325.      * Bytestream connections is enabled. Default is <code>true</code>.
  326.      *
  327.      * @return <code>true</code> if prioritization is enabled, <code>false</code> otherwise
  328.      */
  329.     public boolean isProxyPrioritizationEnabled() {
  330.         return proxyPrioritizationEnabled;
  331.     }

  332.     /**
  333.      * Enable/disable the prioritization of the last working SOCKS5 proxy on successive SOCKS5
  334.      * Bytestream connections.
  335.      *
  336.      * @param proxyPrioritizationEnabled enable/disable the prioritization of the last working
  337.      *        SOCKS5 proxy
  338.      */
  339.     public void setProxyPrioritizationEnabled(boolean proxyPrioritizationEnabled) {
  340.         this.proxyPrioritizationEnabled = proxyPrioritizationEnabled;
  341.     }

  342.     /**
  343.      * Establishes a SOCKS5 Bytestream with the given user and returns the Socket to send/receive
  344.      * data to/from the user.
  345.      * <p>
  346.      * Use this method to establish SOCKS5 Bytestreams to users accepting all incoming Socks5
  347.      * bytestream requests since this method doesn't provide a way to tell the user something about
  348.      * the data to be sent.
  349.      * <p>
  350.      * To establish a SOCKS5 Bytestream after negotiation the kind of data to be sent (e.g. file
  351.      * transfer) use {@link #establishSession(Jid, String)}.
  352.      *
  353.      * @param targetJID the JID of the user a SOCKS5 Bytestream should be established
  354.      * @return the Socket to send/receive data to/from the user
  355.      * @throws XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5
  356.      *         Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies
  357.      * @throws IOException if the bytestream could not be established
  358.      * @throws InterruptedException if the current thread was interrupted while waiting
  359.      * @throws SmackException if there was no response from the server.
  360.      */
  361.     public Socks5BytestreamSession establishSession(Jid targetJID) throws XMPPException,
  362.                     IOException, InterruptedException, SmackException {
  363.         String sessionID = getNextSessionID();
  364.         return establishSession(targetJID, sessionID);
  365.     }

  366.     /**
  367.      * Establishes a SOCKS5 Bytestream with the given user using the given session ID and returns
  368.      * the Socket to send/receive data to/from the user.
  369.      *
  370.      * @param targetJID the JID of the user a SOCKS5 Bytestream should be established
  371.      * @param sessionID the session ID for the SOCKS5 Bytestream request
  372.      * @return the Socket to send/receive data to/from the user
  373.      * @throws IOException if the bytestream could not be established
  374.      * @throws InterruptedException if the current thread was interrupted while waiting
  375.      * @throws NoResponseException
  376.      * @throws SmackException if the target does not support SOCKS5.
  377.      * @throws XMPPException
  378.      */
  379.     public Socks5BytestreamSession establishSession(Jid targetJID, String sessionID)
  380.                     throws IOException, InterruptedException, NoResponseException, SmackException, XMPPException{

  381.         XMPPErrorException discoveryException = null;
  382.         // check if target supports SOCKS5 Bytestream
  383.         if (!supportsSocks5(targetJID)) {
  384.             throw new FeatureNotSupportedException("SOCKS5 Bytestream", targetJID);
  385.         }

  386.         List<Jid> proxies = new ArrayList<>();
  387.         // determine SOCKS5 proxies from XMPP-server
  388.         try {
  389.             proxies.addAll(determineProxies());
  390.         } catch (XMPPErrorException e) {
  391.             // don't abort here, just remember the exception thrown by determineProxies()
  392.             // determineStreamHostInfos() will at least add the local Socks5 proxy (if enabled)
  393.             discoveryException = e;
  394.         }

  395.         // determine address and port of each proxy
  396.         List<StreamHost> streamHosts = determineStreamHostInfos(proxies);

  397.         if (streamHosts.isEmpty()) {
  398.             if (discoveryException != null) {
  399.                 throw discoveryException;
  400.             } else {
  401.                 throw new SmackException("no SOCKS5 proxies available");
  402.             }
  403.         }

  404.         // compute digest
  405.         String digest = Socks5Utils.createDigest(sessionID, this.connection.getUser(), targetJID);

  406.         // prioritize last working SOCKS5 proxy if exists
  407.         if (this.proxyPrioritizationEnabled && this.lastWorkingProxy != null) {
  408.             StreamHost selectedStreamHost = null;
  409.             for (StreamHost streamHost : streamHosts) {
  410.                 if (streamHost.getJID().equals(this.lastWorkingProxy)) {
  411.                     selectedStreamHost = streamHost;
  412.                     break;
  413.                 }
  414.             }
  415.             if (selectedStreamHost != null) {
  416.                 streamHosts.remove(selectedStreamHost);
  417.                 streamHosts.add(0, selectedStreamHost);
  418.             }

  419.         }

  420.         Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy();
  421.         try {

  422.             // add transfer digest to local proxy to make transfer valid
  423.             socks5Proxy.addTransfer(digest);

  424.             // create initiation packet
  425.             Bytestream initiation = createBytestreamInitiation(sessionID, targetJID, streamHosts);

  426.             // send initiation packet
  427.             Stanza response = connection.createPacketCollectorAndSend(initiation).nextResultOrThrow(
  428.                             getTargetResponseTimeout());

  429.             // extract used stream host from response
  430.             StreamHostUsed streamHostUsed = ((Bytestream) response).getUsedHost();
  431.             StreamHost usedStreamHost = initiation.getStreamHost(streamHostUsed.getJID());

  432.             if (usedStreamHost == null) {
  433.                 throw new SmackException("Remote user responded with unknown host");
  434.             }

  435.             // build SOCKS5 client
  436.             Socks5Client socks5Client = new Socks5ClientForInitiator(usedStreamHost, digest,
  437.                             this.connection, sessionID, targetJID);

  438.             // establish connection to proxy
  439.             Socket socket = socks5Client.getSocket(getProxyConnectionTimeout());

  440.             // remember last working SOCKS5 proxy to prioritize it for next request
  441.             this.lastWorkingProxy = usedStreamHost.getJID();

  442.             // negotiation successful, return the output stream
  443.             return new Socks5BytestreamSession(socket, usedStreamHost.getJID().equals(
  444.                             this.connection.getUser()));

  445.         }
  446.         catch (TimeoutException e) {
  447.             throw new IOException("Timeout while connecting to SOCKS5 proxy");
  448.         }
  449.         finally {

  450.             // remove transfer digest if output stream is returned or an exception
  451.             // occurred
  452.             socks5Proxy.removeTransfer(digest);

  453.         }
  454.     }

  455.     /**
  456.      * Returns <code>true</code> if the given target JID supports feature SOCKS5 Bytestream.
  457.      *
  458.      * @param targetJID the target JID
  459.      * @return <code>true</code> if the given target JID supports feature SOCKS5 Bytestream
  460.      *         otherwise <code>false</code>
  461.      * @throws XMPPErrorException
  462.      * @throws NoResponseException
  463.      * @throws NotConnectedException
  464.      * @throws InterruptedException
  465.      */
  466.     private boolean supportsSocks5(Jid targetJID) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  467.         return ServiceDiscoveryManager.getInstanceFor(connection).supportsFeature(targetJID, Bytestream.NAMESPACE);
  468.     }

  469.     /**
  470.      * Returns a list of JIDs of SOCKS5 proxies by querying the XMPP server. The SOCKS5 proxies are
  471.      * in the same order as returned by the XMPP server.
  472.      *
  473.      * @return list of JIDs of SOCKS5 proxies
  474.      * @throws XMPPErrorException if there was an error querying the XMPP server for SOCKS5 proxies
  475.      * @throws NoResponseException if there was no response from the server.
  476.      * @throws NotConnectedException
  477.      * @throws InterruptedException
  478.      */
  479.     private List<Jid> determineProxies() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  480.         ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection);

  481.         List<Jid> proxies = new ArrayList<>();

  482.         // get all items from XMPP server
  483.         DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(this.connection.getServiceName());

  484.         // query all items if they are SOCKS5 proxies
  485.         for (Item item : discoverItems.getItems()) {
  486.             // skip blacklisted servers
  487.             if (this.proxyBlacklist.contains(item.getEntityID())) {
  488.                 continue;
  489.             }

  490.             DiscoverInfo proxyInfo;
  491.             try {
  492.                 proxyInfo = serviceDiscoveryManager.discoverInfo(item.getEntityID());
  493.             }
  494.             catch (NoResponseException|XMPPErrorException e) {
  495.                 // blacklist errornous server
  496.                 proxyBlacklist.add(item.getEntityID());
  497.                 continue;
  498.             }

  499.             if (proxyInfo.hasIdentity("proxy", "bytestreams")) {
  500.                 proxies.add(item.getEntityID());
  501.             } else {
  502.                 /*
  503.                  * server is not a SOCKS5 proxy, blacklist server to skip next time a Socks5
  504.                  * bytestream should be established
  505.                  */
  506.                 this.proxyBlacklist.add(item.getEntityID());
  507.             }
  508.         }

  509.         return proxies;
  510.     }

  511.     /**
  512.      * Returns a list of stream hosts containing the IP address an the port for the given list of
  513.      * SOCKS5 proxy JIDs. The order of the returned list is the same as the given list of JIDs
  514.      * excluding all SOCKS5 proxies who's network settings could not be determined. If a local
  515.      * SOCKS5 proxy is running it will be the first item in the list returned.
  516.      *
  517.      * @param proxies a list of SOCKS5 proxy JIDs
  518.      * @return a list of stream hosts containing the IP address an the port
  519.      */
  520.     private List<StreamHost> determineStreamHostInfos(List<Jid> proxies) {
  521.         List<StreamHost> streamHosts = new ArrayList<StreamHost>();

  522.         // add local proxy on first position if exists
  523.         List<StreamHost> localProxies = getLocalStreamHost();
  524.         if (localProxies != null) {
  525.             streamHosts.addAll(localProxies);
  526.         }

  527.         // query SOCKS5 proxies for network settings
  528.         for (Jid proxy : proxies) {
  529.             Bytestream streamHostRequest = createStreamHostRequest(proxy);
  530.             try {
  531.                 Bytestream response = (Bytestream) connection.createPacketCollectorAndSend(
  532.                                 streamHostRequest).nextResultOrThrow();
  533.                 streamHosts.addAll(response.getStreamHosts());
  534.             }
  535.             catch (Exception e) {
  536.                 // blacklist errornous proxies
  537.                 this.proxyBlacklist.add(proxy);
  538.             }
  539.         }

  540.         return streamHosts;
  541.     }

  542.     /**
  543.      * Returns a IQ packet to query a SOCKS5 proxy its network settings.
  544.      *
  545.      * @param proxy the proxy to query
  546.      * @return IQ packet to query a SOCKS5 proxy its network settings
  547.      */
  548.     private Bytestream createStreamHostRequest(Jid proxy) {
  549.         Bytestream request = new Bytestream();
  550.         request.setType(IQ.Type.get);
  551.         request.setTo(proxy);
  552.         return request;
  553.     }

  554.     /**
  555.      * Returns the stream host information of the local SOCKS5 proxy containing the IP address and
  556.      * the port or null if local SOCKS5 proxy is not running.
  557.      *
  558.      * @return the stream host information of the local SOCKS5 proxy or null if local SOCKS5 proxy
  559.      *         is not running
  560.      */
  561.     private List<StreamHost> getLocalStreamHost() {

  562.         // get local proxy singleton
  563.         Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy();

  564.         if (!socks5Server.isRunning()) {
  565.             // server is not running
  566.             return null;
  567.         }
  568.         List<String> addresses = socks5Server.getLocalAddresses();
  569.         if (addresses.isEmpty()) {
  570.             // local address could not be determined
  571.             return null;
  572.         }
  573.         final int port = socks5Server.getPort();

  574.         List<StreamHost> streamHosts = new ArrayList<StreamHost>();
  575.         outerloop: for (String address : addresses) {
  576.             // Prevent loopback addresses from appearing as streamhost
  577.             final String[] loopbackAddresses = { "127.0.0.1", "0:0:0:0:0:0:0:1", "::1" };
  578.             for (String loopbackAddress : loopbackAddresses) {
  579.                 // Use 'startsWith' here since IPv6 addresses may have scope ID,
  580.                 // ie. the part after the '%' sign.
  581.                 if (address.startsWith(loopbackAddress)) {
  582.                     continue outerloop;
  583.                 }
  584.             }
  585.             streamHosts.add(new StreamHost(connection.getUser(), address, port));
  586.         }
  587.         return streamHosts;
  588.     }

  589.     /**
  590.      * Returns a SOCKS5 Bytestream initialization request packet with the given session ID
  591.      * containing the given stream hosts for the given target JID.
  592.      *
  593.      * @param sessionID the session ID for the SOCKS5 Bytestream
  594.      * @param targetJID the target JID of SOCKS5 Bytestream request
  595.      * @param streamHosts a list of SOCKS5 proxies the target should connect to
  596.      * @return a SOCKS5 Bytestream initialization request packet
  597.      */
  598.     private Bytestream createBytestreamInitiation(String sessionID, Jid targetJID,
  599.                     List<StreamHost> streamHosts) {
  600.         Bytestream initiation = new Bytestream(sessionID);

  601.         // add all stream hosts
  602.         for (StreamHost streamHost : streamHosts) {
  603.             initiation.addStreamHost(streamHost);
  604.         }

  605.         initiation.setType(IQ.Type.set);
  606.         initiation.setTo(targetJID);

  607.         return initiation;
  608.     }

  609.     /**
  610.      * Responses to the given packet's sender with an XMPP error that a SOCKS5 Bytestream is not
  611.      * accepted.
  612.      * <p>
  613.      * Specified in XEP-65 5.3.1 (Example 13)
  614.      * </p>
  615.      *
  616.      * @param packet Packet that should be answered with a not-acceptable error
  617.      * @throws NotConnectedException
  618.      * @throws InterruptedException
  619.      */
  620.     protected void replyRejectPacket(IQ packet) throws NotConnectedException, InterruptedException {
  621.         XMPPError xmppError = new XMPPError(XMPPError.Condition.not_acceptable);
  622.         IQ errorIQ = IQ.createErrorResponse(packet, xmppError);
  623.         this.connection.sendStanza(errorIQ);
  624.     }

  625.     /**
  626.      * Activates the Socks5BytestreamManager by registering the SOCKS5 Bytestream initialization
  627.      * listener and enabling the SOCKS5 Bytestream feature.
  628.      */
  629.     private void activate() {
  630.         // register bytestream initiation packet listener
  631.         connection.registerIQRequestHandler(initiationListener);

  632.         // enable SOCKS5 feature
  633.         enableService();
  634.     }

  635.     /**
  636.      * Adds the SOCKS5 Bytestream feature to the service discovery.
  637.      */
  638.     private void enableService() {
  639.         ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(this.connection);
  640.         manager.addFeature(Bytestream.NAMESPACE);
  641.     }

  642.     /**
  643.      * Returns a new unique session ID.
  644.      *
  645.      * @return a new unique session ID
  646.      */
  647.     private String getNextSessionID() {
  648.         StringBuilder buffer = new StringBuilder();
  649.         buffer.append(SESSION_ID_PREFIX);
  650.         buffer.append(Math.abs(randomGenerator.nextLong()));
  651.         return buffer.toString();
  652.     }

  653.     /**
  654.      * Returns the XMPP connection.
  655.      *
  656.      * @return the XMPP connection
  657.      */
  658.     protected XMPPConnection getConnection() {
  659.         return this.connection;
  660.     }

  661.     /**
  662.      * Returns the {@link BytestreamListener} that should be informed if a SOCKS5 Bytestream request
  663.      * from the given initiator JID is received.
  664.      *
  665.      * @param initiator the initiator's JID
  666.      * @return the listener
  667.      */
  668.     protected BytestreamListener getUserListener(Jid initiator) {
  669.         return this.userListeners.get(initiator);
  670.     }

  671.     /**
  672.      * Returns a list of {@link BytestreamListener} that are informed if there are no listeners for
  673.      * a specific initiator.
  674.      *
  675.      * @return list of listeners
  676.      */
  677.     protected List<BytestreamListener> getAllRequestListeners() {
  678.         return this.allRequestListeners;
  679.     }

  680.     /**
  681.      * Returns the list of session IDs that should be ignored by the InitialtionListener
  682.      *
  683.      * @return list of session IDs
  684.      */
  685.     protected List<String> getIgnoredBytestreamRequests() {
  686.         return ignoredBytestreamRequests;
  687.     }

  688. }