Home | History | Annotate | Download | only in socks5
      1 /**
      2  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
      3  * you may not use this file except in compliance with the License.
      4  * You may obtain a copy of the License at
      5  *
      6  *     http://www.apache.org/licenses/LICENSE-2.0
      7  *
      8  * Unless required by applicable law or agreed to in writing, software
      9  * distributed under the License is distributed on an "AS IS" BASIS,
     10  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     11  * See the License for the specific language governing permissions and
     12  * limitations under the License.
     13  */
     14 package org.jivesoftware.smackx.bytestreams.socks5;
     15 
     16 import java.io.IOException;
     17 import java.lang.ref.WeakReference;
     18 import java.net.Socket;
     19 import java.util.ArrayList;
     20 import java.util.Collections;
     21 import java.util.Iterator;
     22 import java.util.LinkedList;
     23 import java.util.List;
     24 import java.util.Map;
     25 import java.util.Random;
     26 import java.util.WeakHashMap;
     27 import java.util.concurrent.ConcurrentHashMap;
     28 import java.util.concurrent.TimeoutException;
     29 
     30 import org.jivesoftware.smack.AbstractConnectionListener;
     31 import org.jivesoftware.smack.Connection;
     32 import org.jivesoftware.smack.ConnectionCreationListener;
     33 import org.jivesoftware.smack.XMPPException;
     34 import org.jivesoftware.smack.packet.IQ;
     35 import org.jivesoftware.smack.packet.Packet;
     36 import org.jivesoftware.smack.packet.XMPPError;
     37 import org.jivesoftware.smack.util.SyncPacketSend;
     38 import org.jivesoftware.smackx.ServiceDiscoveryManager;
     39 import org.jivesoftware.smackx.bytestreams.BytestreamListener;
     40 import org.jivesoftware.smackx.bytestreams.BytestreamManager;
     41 import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
     42 import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost;
     43 import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHostUsed;
     44 import org.jivesoftware.smackx.filetransfer.FileTransferManager;
     45 import org.jivesoftware.smackx.packet.DiscoverInfo;
     46 import org.jivesoftware.smackx.packet.DiscoverItems;
     47 import org.jivesoftware.smackx.packet.DiscoverInfo.Identity;
     48 import org.jivesoftware.smackx.packet.DiscoverItems.Item;
     49 
     50 /**
     51  * The Socks5BytestreamManager class handles establishing SOCKS5 Bytestreams as specified in the <a
     52  * href="http://xmpp.org/extensions/xep-0065.html">XEP-0065</a>.
     53  * <p>
     54  * A SOCKS5 Bytestream is negotiated partly over the XMPP XML stream and partly over a separate
     55  * socket. The actual transfer though takes place over a separately created socket.
     56  * <p>
     57  * A SOCKS5 Bytestream generally has three parties, the initiator, the target, and the stream host.
     58  * The stream host is a specialized SOCKS5 proxy setup on a server, or, the initiator can act as the
     59  * stream host.
     60  * <p>
     61  * To establish a SOCKS5 Bytestream invoke the {@link #establishSession(String)} method. This will
     62  * negotiate a SOCKS5 Bytestream with the given target JID and return a socket.
     63  * <p>
     64  * If a session ID for the SOCKS5 Bytestream was already negotiated (e.g. while negotiating a file
     65  * transfer) invoke {@link #establishSession(String, String)}.
     66  * <p>
     67  * To handle incoming SOCKS5 Bytestream requests add an {@link Socks5BytestreamListener} to the
     68  * manager. There are two ways to add this listener. If you want to be informed about incoming
     69  * SOCKS5 Bytestreams from a specific user add the listener by invoking
     70  * {@link #addIncomingBytestreamListener(BytestreamListener, String)}. If the listener should
     71  * respond to all SOCKS5 Bytestream requests invoke
     72  * {@link #addIncomingBytestreamListener(BytestreamListener)}.
     73  * <p>
     74  * Note that the registered {@link Socks5BytestreamListener} will NOT be notified on incoming Socks5
     75  * bytestream requests sent in the context of <a
     76  * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See
     77  * {@link FileTransferManager})
     78  * <p>
     79  * If no {@link Socks5BytestreamListener}s are registered, all incoming SOCKS5 Bytestream requests
     80  * will be rejected by returning a &lt;not-acceptable/&gt; error to the initiator.
     81  *
     82  * @author Henning Staib
     83  */
     84 public final class Socks5BytestreamManager implements BytestreamManager {
     85 
     86     /*
     87      * create a new Socks5BytestreamManager and register a shutdown listener on every established
     88      * connection
     89      */
     90     static {
     91         Connection.addConnectionCreationListener(new ConnectionCreationListener() {
     92 
     93             public void connectionCreated(final Connection connection) {
     94                 final Socks5BytestreamManager manager;
     95                 manager = Socks5BytestreamManager.getBytestreamManager(connection);
     96 
     97                 // register shutdown listener
     98                 connection.addConnectionListener(new AbstractConnectionListener() {
     99 
    100                     public void connectionClosed() {
    101                         manager.disableService();
    102                     }
    103 
    104                     public void connectionClosedOnError(Exception e) {
    105                         manager.disableService();
    106                     }
    107 
    108                     public void reconnectionSuccessful() {
    109                         managers.put(connection, manager);
    110                     }
    111 
    112                 });
    113             }
    114 
    115         });
    116     }
    117 
    118     /**
    119      * The XMPP namespace of the SOCKS5 Bytestream
    120      */
    121     public static final String NAMESPACE = "http://jabber.org/protocol/bytestreams";
    122 
    123     /* prefix used to generate session IDs */
    124     private static final String SESSION_ID_PREFIX = "js5_";
    125 
    126     /* random generator to create session IDs */
    127     private final static Random randomGenerator = new Random();
    128 
    129     /* stores one Socks5BytestreamManager for each XMPP connection */
    130     private final static Map<Connection, Socks5BytestreamManager> managers = new WeakHashMap<Connection, Socks5BytestreamManager>();
    131 
    132     /* XMPP connection */
    133     private final Connection connection;
    134 
    135     /*
    136      * assigns a user to a listener that is informed if a bytestream request for this user is
    137      * received
    138      */
    139     private final Map<String, BytestreamListener> userListeners = new ConcurrentHashMap<String, BytestreamListener>();
    140 
    141     /*
    142      * list of listeners that respond to all bytestream requests if there are not user specific
    143      * listeners for that request
    144      */
    145     private final List<BytestreamListener> allRequestListeners = Collections.synchronizedList(new LinkedList<BytestreamListener>());
    146 
    147     /* listener that handles all incoming bytestream requests */
    148     private final InitiationListener initiationListener;
    149 
    150     /* timeout to wait for the response to the SOCKS5 Bytestream initialization request */
    151     private int targetResponseTimeout = 10000;
    152 
    153     /* timeout for connecting to the SOCKS5 proxy selected by the target */
    154     private int proxyConnectionTimeout = 10000;
    155 
    156     /* blacklist of errornous SOCKS5 proxies */
    157     private final List<String> proxyBlacklist = Collections.synchronizedList(new LinkedList<String>());
    158 
    159     /* remember the last proxy that worked to prioritize it */
    160     private String lastWorkingProxy = null;
    161 
    162     /* flag to enable/disable prioritization of last working proxy */
    163     private boolean proxyPrioritizationEnabled = true;
    164 
    165     /*
    166      * list containing session IDs of SOCKS5 Bytestream initialization packets that should be
    167      * ignored by the InitiationListener
    168      */
    169     private List<String> ignoredBytestreamRequests = Collections.synchronizedList(new LinkedList<String>());
    170 
    171     /**
    172      * Returns the Socks5BytestreamManager to handle SOCKS5 Bytestreams for a given
    173      * {@link Connection}.
    174      * <p>
    175      * If no manager exists a new is created and initialized.
    176      *
    177      * @param connection the XMPP connection or <code>null</code> if given connection is
    178      *        <code>null</code>
    179      * @return the Socks5BytestreamManager for the given XMPP connection
    180      */
    181     public static synchronized Socks5BytestreamManager getBytestreamManager(Connection connection) {
    182         if (connection == null) {
    183             return null;
    184         }
    185         Socks5BytestreamManager manager = managers.get(connection);
    186         if (manager == null) {
    187             manager = new Socks5BytestreamManager(connection);
    188             managers.put(connection, manager);
    189             manager.activate();
    190         }
    191         return manager;
    192     }
    193 
    194     /**
    195      * Private constructor.
    196      *
    197      * @param connection the XMPP connection
    198      */
    199     private Socks5BytestreamManager(Connection connection) {
    200         this.connection = connection;
    201         this.initiationListener = new InitiationListener(this);
    202     }
    203 
    204     /**
    205      * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request unless
    206      * there is a user specific BytestreamListener registered.
    207      * <p>
    208      * If no listeners are registered all SOCKS5 Bytestream request are rejected with a
    209      * &lt;not-acceptable/&gt; error.
    210      * <p>
    211      * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5
    212      * bytestream requests sent in the context of <a
    213      * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See
    214      * {@link FileTransferManager})
    215      *
    216      * @param listener the listener to register
    217      */
    218     public void addIncomingBytestreamListener(BytestreamListener listener) {
    219         this.allRequestListeners.add(listener);
    220     }
    221 
    222     /**
    223      * Removes the given listener from the list of listeners for all incoming SOCKS5 Bytestream
    224      * requests.
    225      *
    226      * @param listener the listener to remove
    227      */
    228     public void removeIncomingBytestreamListener(BytestreamListener listener) {
    229         this.allRequestListeners.remove(listener);
    230     }
    231 
    232     /**
    233      * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request from the
    234      * given user.
    235      * <p>
    236      * Use this method if you are awaiting an incoming SOCKS5 Bytestream request from a specific
    237      * user.
    238      * <p>
    239      * If no listeners are registered all SOCKS5 Bytestream request are rejected with a
    240      * &lt;not-acceptable/&gt; error.
    241      * <p>
    242      * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5
    243      * bytestream requests sent in the context of <a
    244      * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See
    245      * {@link FileTransferManager})
    246      *
    247      * @param listener the listener to register
    248      * @param initiatorJID the JID of the user that wants to establish a SOCKS5 Bytestream
    249      */
    250     public void addIncomingBytestreamListener(BytestreamListener listener, String initiatorJID) {
    251         this.userListeners.put(initiatorJID, listener);
    252     }
    253 
    254     /**
    255      * Removes the listener for the given user.
    256      *
    257      * @param initiatorJID the JID of the user the listener should be removed
    258      */
    259     public void removeIncomingBytestreamListener(String initiatorJID) {
    260         this.userListeners.remove(initiatorJID);
    261     }
    262 
    263     /**
    264      * Use this method to ignore the next incoming SOCKS5 Bytestream request containing the given
    265      * session ID. No listeners will be notified for this request and and no error will be returned
    266      * to the initiator.
    267      * <p>
    268      * This method should be used if you are awaiting a SOCKS5 Bytestream request as a reply to
    269      * another packet (e.g. file transfer).
    270      *
    271      * @param sessionID to be ignored
    272      */
    273     public void ignoreBytestreamRequestOnce(String sessionID) {
    274         this.ignoredBytestreamRequests.add(sessionID);
    275     }
    276 
    277     /**
    278      * Disables the SOCKS5 Bytestream manager by removing the SOCKS5 Bytestream feature from the
    279      * service discovery, disabling the listener for SOCKS5 Bytestream initiation requests and
    280      * resetting its internal state.
    281      * <p>
    282      * To re-enable the SOCKS5 Bytestream feature invoke {@link #getBytestreamManager(Connection)}.
    283      * Using the file transfer API will automatically re-enable the SOCKS5 Bytestream feature.
    284      */
    285     public synchronized void disableService() {
    286 
    287         // remove initiation packet listener
    288         this.connection.removePacketListener(this.initiationListener);
    289 
    290         // shutdown threads
    291         this.initiationListener.shutdown();
    292 
    293         // clear listeners
    294         this.allRequestListeners.clear();
    295         this.userListeners.clear();
    296 
    297         // reset internal state
    298         this.lastWorkingProxy = null;
    299         this.proxyBlacklist.clear();
    300         this.ignoredBytestreamRequests.clear();
    301 
    302         // remove manager from static managers map
    303         managers.remove(this.connection);
    304 
    305         // shutdown local SOCKS5 proxy if there are no more managers for other connections
    306         if (managers.size() == 0) {
    307             Socks5Proxy.getSocks5Proxy().stop();
    308         }
    309 
    310         // remove feature from service discovery
    311         ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection);
    312 
    313         // check if service discovery is not already disposed by connection shutdown
    314         if (serviceDiscoveryManager != null) {
    315             serviceDiscoveryManager.removeFeature(NAMESPACE);
    316         }
    317 
    318     }
    319 
    320     /**
    321      * Returns the timeout to wait for the response to the SOCKS5 Bytestream initialization request.
    322      * Default is 10000ms.
    323      *
    324      * @return the timeout to wait for the response to the SOCKS5 Bytestream initialization request
    325      */
    326     public int getTargetResponseTimeout() {
    327         if (this.targetResponseTimeout <= 0) {
    328             this.targetResponseTimeout = 10000;
    329         }
    330         return targetResponseTimeout;
    331     }
    332 
    333     /**
    334      * Sets the timeout to wait for the response to the SOCKS5 Bytestream initialization request.
    335      * Default is 10000ms.
    336      *
    337      * @param targetResponseTimeout the timeout to set
    338      */
    339     public void setTargetResponseTimeout(int targetResponseTimeout) {
    340         this.targetResponseTimeout = targetResponseTimeout;
    341     }
    342 
    343     /**
    344      * Returns the timeout for connecting to the SOCKS5 proxy selected by the target. Default is
    345      * 10000ms.
    346      *
    347      * @return the timeout for connecting to the SOCKS5 proxy selected by the target
    348      */
    349     public int getProxyConnectionTimeout() {
    350         if (this.proxyConnectionTimeout <= 0) {
    351             this.proxyConnectionTimeout = 10000;
    352         }
    353         return proxyConnectionTimeout;
    354     }
    355 
    356     /**
    357      * Sets the timeout for connecting to the SOCKS5 proxy selected by the target. Default is
    358      * 10000ms.
    359      *
    360      * @param proxyConnectionTimeout the timeout to set
    361      */
    362     public void setProxyConnectionTimeout(int proxyConnectionTimeout) {
    363         this.proxyConnectionTimeout = proxyConnectionTimeout;
    364     }
    365 
    366     /**
    367      * Returns if the prioritization of the last working SOCKS5 proxy on successive SOCKS5
    368      * Bytestream connections is enabled. Default is <code>true</code>.
    369      *
    370      * @return <code>true</code> if prioritization is enabled, <code>false</code> otherwise
    371      */
    372     public boolean isProxyPrioritizationEnabled() {
    373         return proxyPrioritizationEnabled;
    374     }
    375 
    376     /**
    377      * Enable/disable the prioritization of the last working SOCKS5 proxy on successive SOCKS5
    378      * Bytestream connections.
    379      *
    380      * @param proxyPrioritizationEnabled enable/disable the prioritization of the last working
    381      *        SOCKS5 proxy
    382      */
    383     public void setProxyPrioritizationEnabled(boolean proxyPrioritizationEnabled) {
    384         this.proxyPrioritizationEnabled = proxyPrioritizationEnabled;
    385     }
    386 
    387     /**
    388      * Establishes a SOCKS5 Bytestream with the given user and returns the Socket to send/receive
    389      * data to/from the user.
    390      * <p>
    391      * Use this method to establish SOCKS5 Bytestreams to users accepting all incoming Socks5
    392      * bytestream requests since this method doesn't provide a way to tell the user something about
    393      * the data to be sent.
    394      * <p>
    395      * To establish a SOCKS5 Bytestream after negotiation the kind of data to be sent (e.g. file
    396      * transfer) use {@link #establishSession(String, String)}.
    397      *
    398      * @param targetJID the JID of the user a SOCKS5 Bytestream should be established
    399      * @return the Socket to send/receive data to/from the user
    400      * @throws XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5
    401      *         Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies
    402      * @throws IOException if the bytestream could not be established
    403      * @throws InterruptedException if the current thread was interrupted while waiting
    404      */
    405     public Socks5BytestreamSession establishSession(String targetJID) throws XMPPException,
    406                     IOException, InterruptedException {
    407         String sessionID = getNextSessionID();
    408         return establishSession(targetJID, sessionID);
    409     }
    410 
    411     /**
    412      * Establishes a SOCKS5 Bytestream with the given user using the given session ID and returns
    413      * the Socket to send/receive data to/from the user.
    414      *
    415      * @param targetJID the JID of the user a SOCKS5 Bytestream should be established
    416      * @param sessionID the session ID for the SOCKS5 Bytestream request
    417      * @return the Socket to send/receive data to/from the user
    418      * @throws XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5
    419      *         Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies
    420      * @throws IOException if the bytestream could not be established
    421      * @throws InterruptedException if the current thread was interrupted while waiting
    422      */
    423     public Socks5BytestreamSession establishSession(String targetJID, String sessionID)
    424                     throws XMPPException, IOException, InterruptedException {
    425 
    426         XMPPException discoveryException = null;
    427         // check if target supports SOCKS5 Bytestream
    428         if (!supportsSocks5(targetJID)) {
    429             throw new XMPPException(targetJID + " doesn't support SOCKS5 Bytestream");
    430         }
    431 
    432         List<String> proxies = new ArrayList<String>();
    433         // determine SOCKS5 proxies from XMPP-server
    434         try {
    435             proxies.addAll(determineProxies());
    436         } catch (XMPPException e) {
    437             // don't abort here, just remember the exception thrown by determineProxies()
    438             // determineStreamHostInfos() will at least add the local Socks5 proxy (if enabled)
    439             discoveryException = e;
    440         }
    441 
    442         // determine address and port of each proxy
    443         List<StreamHost> streamHosts = determineStreamHostInfos(proxies);
    444 
    445         if (streamHosts.isEmpty()) {
    446             throw discoveryException != null ? discoveryException : new XMPPException("no SOCKS5 proxies available");
    447         }
    448 
    449         // compute digest
    450         String digest = Socks5Utils.createDigest(sessionID, this.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             Packet response = SyncPacketSend.getReply(this.connection, initiation,
    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 XMPPException("Remote user responded with unknown host");
    487             }
    488 
    489             // build SOCKS5 client
    490             Socks5Client socks5Client = new Socks5ClientForInitiator(usedStreamHost, digest,
    491                             this.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                             this.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 XMPPException if there was an error querying target for supported features
    523      */
    524     private boolean supportsSocks5(String targetJID) throws XMPPException {
    525         ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection);
    526         DiscoverInfo discoverInfo = serviceDiscoveryManager.discoverInfo(targetJID);
    527         return discoverInfo.containsFeature(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 XMPPException if there was an error querying the XMPP server for SOCKS5 proxies
    536      */
    537     private List<String> determineProxies() throws XMPPException {
    538         ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection);
    539 
    540         List<String> proxies = new ArrayList<String>();
    541 
    542         // get all items form XMPP server
    543         DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(this.connection.getServiceName());
    544         Iterator<Item> itemIterator = discoverItems.getItems();
    545 
    546         // query all items if they are SOCKS5 proxies
    547         while (itemIterator.hasNext()) {
    548             Item item = itemIterator.next();
    549 
    550             // skip blacklisted servers
    551             if (this.proxyBlacklist.contains(item.getEntityID())) {
    552                 continue;
    553             }
    554 
    555             try {
    556                 DiscoverInfo proxyInfo;
    557                 proxyInfo = serviceDiscoveryManager.discoverInfo(item.getEntityID());
    558                 Iterator<Identity> identities = proxyInfo.getIdentities();
    559 
    560                 // item must have category "proxy" and type "bytestream"
    561                 while (identities.hasNext()) {
    562                     Identity identity = identities.next();
    563 
    564                     if ("proxy".equalsIgnoreCase(identity.getCategory())
    565                                     && "bytestreams".equalsIgnoreCase(identity.getType())) {
    566                         proxies.add(item.getEntityID());
    567                         break;
    568                     }
    569 
    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             catch (XMPPException e) {
    579                 // blacklist errornous server
    580                 this.proxyBlacklist.add(item.getEntityID());
    581             }
    582         }
    583 
    584         return proxies;
    585     }
    586 
    587     /**
    588      * Returns a list of stream hosts containing the IP address an the port for the given list of
    589      * SOCKS5 proxy JIDs. The order of the returned list is the same as the given list of JIDs
    590      * excluding all SOCKS5 proxies who's network settings could not be determined. If a local
    591      * SOCKS5 proxy is running it will be the first item in the list returned.
    592      *
    593      * @param proxies a list of SOCKS5 proxy JIDs
    594      * @return a list of stream hosts containing the IP address an the port
    595      */
    596     private List<StreamHost> determineStreamHostInfos(List<String> proxies) {
    597         List<StreamHost> streamHosts = new ArrayList<StreamHost>();
    598 
    599         // add local proxy on first position if exists
    600         List<StreamHost> localProxies = getLocalStreamHost();
    601         if (localProxies != null) {
    602             streamHosts.addAll(localProxies);
    603         }
    604 
    605         // query SOCKS5 proxies for network settings
    606         for (String proxy : proxies) {
    607             Bytestream streamHostRequest = createStreamHostRequest(proxy);
    608             try {
    609                 Bytestream response = (Bytestream) SyncPacketSend.getReply(this.connection,
    610                                 streamHostRequest);
    611                 streamHosts.addAll(response.getStreamHosts());
    612             }
    613             catch (XMPPException e) {
    614                 // blacklist errornous proxies
    615                 this.proxyBlacklist.add(proxy);
    616             }
    617         }
    618 
    619         return streamHosts;
    620     }
    621 
    622     /**
    623      * Returns a IQ packet to query a SOCKS5 proxy its network settings.
    624      *
    625      * @param proxy the proxy to query
    626      * @return IQ packet to query a SOCKS5 proxy its network settings
    627      */
    628     private Bytestream createStreamHostRequest(String proxy) {
    629         Bytestream request = new Bytestream();
    630         request.setType(IQ.Type.GET);
    631         request.setTo(proxy);
    632         return request;
    633     }
    634 
    635     /**
    636      * Returns the stream host information of the local SOCKS5 proxy containing the IP address and
    637      * the port or null if local SOCKS5 proxy is not running.
    638      *
    639      * @return the stream host information of the local SOCKS5 proxy or null if local SOCKS5 proxy
    640      *         is not running
    641      */
    642     private List<StreamHost> getLocalStreamHost() {
    643 
    644         // get local proxy singleton
    645         Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy();
    646 
    647         if (socks5Server.isRunning()) {
    648             List<String> addresses = socks5Server.getLocalAddresses();
    649             int port = socks5Server.getPort();
    650 
    651             if (addresses.size() >= 1) {
    652                 List<StreamHost> streamHosts = new ArrayList<StreamHost>();
    653                 for (String address : addresses) {
    654                     StreamHost streamHost = new StreamHost(this.connection.getUser(), address);
    655                     streamHost.setPort(port);
    656                     streamHosts.add(streamHost);
    657                 }
    658                 return streamHosts;
    659             }
    660 
    661         }
    662 
    663         // server is not running or local address could not be determined
    664         return null;
    665     }
    666 
    667     /**
    668      * Returns a SOCKS5 Bytestream initialization request packet with the given session ID
    669      * containing the given stream hosts for the given target JID.
    670      *
    671      * @param sessionID the session ID for the SOCKS5 Bytestream
    672      * @param targetJID the target JID of SOCKS5 Bytestream request
    673      * @param streamHosts a list of SOCKS5 proxies the target should connect to
    674      * @return a SOCKS5 Bytestream initialization request packet
    675      */
    676     private Bytestream createBytestreamInitiation(String sessionID, String targetJID,
    677                     List<StreamHost> streamHosts) {
    678         Bytestream initiation = new Bytestream(sessionID);
    679 
    680         // add all stream hosts
    681         for (StreamHost streamHost : streamHosts) {
    682             initiation.addStreamHost(streamHost);
    683         }
    684 
    685         initiation.setType(IQ.Type.SET);
    686         initiation.setTo(targetJID);
    687 
    688         return initiation;
    689     }
    690 
    691     /**
    692      * Responses to the given packet's sender with a XMPP error that a SOCKS5 Bytestream is not
    693      * accepted.
    694      *
    695      * @param packet Packet that should be answered with a not-acceptable error
    696      */
    697     protected void replyRejectPacket(IQ packet) {
    698         XMPPError xmppError = new XMPPError(XMPPError.Condition.no_acceptable);
    699         IQ errorIQ = IQ.createErrorResponse(packet, xmppError);
    700         this.connection.sendPacket(errorIQ);
    701     }
    702 
    703     /**
    704      * Activates the Socks5BytestreamManager by registering the SOCKS5 Bytestream initialization
    705      * listener and enabling the SOCKS5 Bytestream feature.
    706      */
    707     private void activate() {
    708         // register bytestream initiation packet listener
    709         this.connection.addPacketListener(this.initiationListener,
    710                         this.initiationListener.getFilter());
    711 
    712         // enable SOCKS5 feature
    713         enableService();
    714     }
    715 
    716     /**
    717      * Adds the SOCKS5 Bytestream feature to the service discovery.
    718      */
    719     private void enableService() {
    720         ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(this.connection);
    721         if (!manager.includesFeature(NAMESPACE)) {
    722             manager.addFeature(NAMESPACE);
    723         }
    724     }
    725 
    726     /**
    727      * Returns a new unique session ID.
    728      *
    729      * @return a new unique session ID
    730      */
    731     private String getNextSessionID() {
    732         StringBuilder buffer = new StringBuilder();
    733         buffer.append(SESSION_ID_PREFIX);
    734         buffer.append(Math.abs(randomGenerator.nextLong()));
    735         return buffer.toString();
    736     }
    737 
    738     /**
    739      * Returns the XMPP connection.
    740      *
    741      * @return the XMPP connection
    742      */
    743     protected Connection getConnection() {
    744         return this.connection;
    745     }
    746 
    747     /**
    748      * Returns the {@link BytestreamListener} that should be informed if a SOCKS5 Bytestream request
    749      * from the given initiator JID is received.
    750      *
    751      * @param initiator the initiator's JID
    752      * @return the listener
    753      */
    754     protected BytestreamListener getUserListener(String initiator) {
    755         return this.userListeners.get(initiator);
    756     }
    757 
    758     /**
    759      * Returns a list of {@link BytestreamListener} that are informed if there are no listeners for
    760      * a specific initiator.
    761      *
    762      * @return list of listeners
    763      */
    764     protected List<BytestreamListener> getAllRequestListeners() {
    765         return this.allRequestListeners;
    766     }
    767 
    768     /**
    769      * Returns the list of session IDs that should be ignored by the InitialtionListener
    770      *
    771      * @return list of session IDs
    772      */
    773     protected List<String> getIgnoredBytestreamRequests() {
    774         return ignoredBytestreamRequests;
    775     }
    776 
    777 }
    778