/** | |
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.jivesoftware.smackx.bytestreams.socks5; | |
import java.io.IOException; | |
import java.lang.ref.WeakReference; | |
import java.net.Socket; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.Iterator; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Random; | |
import java.util.WeakHashMap; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.TimeoutException; | |
import org.jivesoftware.smack.AbstractConnectionListener; | |
import org.jivesoftware.smack.Connection; | |
import org.jivesoftware.smack.ConnectionCreationListener; | |
import org.jivesoftware.smack.XMPPException; | |
import org.jivesoftware.smack.packet.IQ; | |
import org.jivesoftware.smack.packet.Packet; | |
import org.jivesoftware.smack.packet.XMPPError; | |
import org.jivesoftware.smack.util.SyncPacketSend; | |
import org.jivesoftware.smackx.ServiceDiscoveryManager; | |
import org.jivesoftware.smackx.bytestreams.BytestreamListener; | |
import org.jivesoftware.smackx.bytestreams.BytestreamManager; | |
import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; | |
import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; | |
import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHostUsed; | |
import org.jivesoftware.smackx.filetransfer.FileTransferManager; | |
import org.jivesoftware.smackx.packet.DiscoverInfo; | |
import org.jivesoftware.smackx.packet.DiscoverItems; | |
import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; | |
import org.jivesoftware.smackx.packet.DiscoverItems.Item; | |
/** | |
* The Socks5BytestreamManager class handles establishing SOCKS5 Bytestreams as specified in the <a | |
* href="http://xmpp.org/extensions/xep-0065.html">XEP-0065</a>. | |
* <p> | |
* A SOCKS5 Bytestream is negotiated partly over the XMPP XML stream and partly over a separate | |
* socket. The actual transfer though takes place over a separately created socket. | |
* <p> | |
* A SOCKS5 Bytestream generally has three parties, the initiator, the target, and the stream host. | |
* The stream host is a specialized SOCKS5 proxy setup on a server, or, the initiator can act as the | |
* stream host. | |
* <p> | |
* To establish a SOCKS5 Bytestream invoke the {@link #establishSession(String)} method. This will | |
* negotiate a SOCKS5 Bytestream with the given target JID and return a socket. | |
* <p> | |
* If a session ID for the SOCKS5 Bytestream was already negotiated (e.g. while negotiating a file | |
* transfer) invoke {@link #establishSession(String, String)}. | |
* <p> | |
* To handle incoming SOCKS5 Bytestream requests add an {@link Socks5BytestreamListener} to the | |
* manager. There are two ways to add this listener. If you want to be informed about incoming | |
* SOCKS5 Bytestreams from a specific user add the listener by invoking | |
* {@link #addIncomingBytestreamListener(BytestreamListener, String)}. If the listener should | |
* respond to all SOCKS5 Bytestream requests invoke | |
* {@link #addIncomingBytestreamListener(BytestreamListener)}. | |
* <p> | |
* Note that the registered {@link Socks5BytestreamListener} will NOT be notified on incoming Socks5 | |
* bytestream requests sent in the context of <a | |
* href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See | |
* {@link FileTransferManager}) | |
* <p> | |
* If no {@link Socks5BytestreamListener}s are registered, all incoming SOCKS5 Bytestream requests | |
* will be rejected by returning a <not-acceptable/> error to the initiator. | |
* | |
* @author Henning Staib | |
*/ | |
public final class Socks5BytestreamManager implements BytestreamManager { | |
/* | |
* create a new Socks5BytestreamManager and register a shutdown listener on every established | |
* connection | |
*/ | |
static { | |
Connection.addConnectionCreationListener(new ConnectionCreationListener() { | |
public void connectionCreated(final Connection connection) { | |
final Socks5BytestreamManager manager; | |
manager = Socks5BytestreamManager.getBytestreamManager(connection); | |
// register shutdown listener | |
connection.addConnectionListener(new AbstractConnectionListener() { | |
public void connectionClosed() { | |
manager.disableService(); | |
} | |
public void connectionClosedOnError(Exception e) { | |
manager.disableService(); | |
} | |
public void reconnectionSuccessful() { | |
managers.put(connection, manager); | |
} | |
}); | |
} | |
}); | |
} | |
/** | |
* The XMPP namespace of the SOCKS5 Bytestream | |
*/ | |
public static final String NAMESPACE = "http://jabber.org/protocol/bytestreams"; | |
/* prefix used to generate session IDs */ | |
private static final String SESSION_ID_PREFIX = "js5_"; | |
/* random generator to create session IDs */ | |
private final static Random randomGenerator = new Random(); | |
/* stores one Socks5BytestreamManager for each XMPP connection */ | |
private final static Map<Connection, Socks5BytestreamManager> managers = new WeakHashMap<Connection, Socks5BytestreamManager>(); | |
/* XMPP connection */ | |
private final Connection connection; | |
/* | |
* assigns a user to a listener that is informed if a bytestream request for this user is | |
* received | |
*/ | |
private final Map<String, BytestreamListener> userListeners = new ConcurrentHashMap<String, BytestreamListener>(); | |
/* | |
* list of listeners that respond to all bytestream requests if there are not user specific | |
* listeners for that request | |
*/ | |
private final List<BytestreamListener> allRequestListeners = Collections.synchronizedList(new LinkedList<BytestreamListener>()); | |
/* listener that handles all incoming bytestream requests */ | |
private final InitiationListener initiationListener; | |
/* timeout to wait for the response to the SOCKS5 Bytestream initialization request */ | |
private int targetResponseTimeout = 10000; | |
/* timeout for connecting to the SOCKS5 proxy selected by the target */ | |
private int proxyConnectionTimeout = 10000; | |
/* blacklist of errornous SOCKS5 proxies */ | |
private final List<String> proxyBlacklist = Collections.synchronizedList(new LinkedList<String>()); | |
/* remember the last proxy that worked to prioritize it */ | |
private String lastWorkingProxy = null; | |
/* flag to enable/disable prioritization of last working proxy */ | |
private boolean proxyPrioritizationEnabled = true; | |
/* | |
* list containing session IDs of SOCKS5 Bytestream initialization packets that should be | |
* ignored by the InitiationListener | |
*/ | |
private List<String> ignoredBytestreamRequests = Collections.synchronizedList(new LinkedList<String>()); | |
/** | |
* Returns the Socks5BytestreamManager to handle SOCKS5 Bytestreams for a given | |
* {@link Connection}. | |
* <p> | |
* If no manager exists a new is created and initialized. | |
* | |
* @param connection the XMPP connection or <code>null</code> if given connection is | |
* <code>null</code> | |
* @return the Socks5BytestreamManager for the given XMPP connection | |
*/ | |
public static synchronized Socks5BytestreamManager getBytestreamManager(Connection connection) { | |
if (connection == null) { | |
return null; | |
} | |
Socks5BytestreamManager manager = managers.get(connection); | |
if (manager == null) { | |
manager = new Socks5BytestreamManager(connection); | |
managers.put(connection, manager); | |
manager.activate(); | |
} | |
return manager; | |
} | |
/** | |
* Private constructor. | |
* | |
* @param connection the XMPP connection | |
*/ | |
private Socks5BytestreamManager(Connection connection) { | |
this.connection = connection; | |
this.initiationListener = new InitiationListener(this); | |
} | |
/** | |
* Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request unless | |
* there is a user specific BytestreamListener registered. | |
* <p> | |
* If no listeners are registered all SOCKS5 Bytestream request are rejected with a | |
* <not-acceptable/> error. | |
* <p> | |
* Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5 | |
* bytestream requests sent in the context of <a | |
* href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See | |
* {@link FileTransferManager}) | |
* | |
* @param listener the listener to register | |
*/ | |
public void addIncomingBytestreamListener(BytestreamListener listener) { | |
this.allRequestListeners.add(listener); | |
} | |
/** | |
* Removes the given listener from the list of listeners for all incoming SOCKS5 Bytestream | |
* requests. | |
* | |
* @param listener the listener to remove | |
*/ | |
public void removeIncomingBytestreamListener(BytestreamListener listener) { | |
this.allRequestListeners.remove(listener); | |
} | |
/** | |
* Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request from the | |
* given user. | |
* <p> | |
* Use this method if you are awaiting an incoming SOCKS5 Bytestream request from a specific | |
* user. | |
* <p> | |
* If no listeners are registered all SOCKS5 Bytestream request are rejected with a | |
* <not-acceptable/> error. | |
* <p> | |
* Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5 | |
* bytestream requests sent in the context of <a | |
* href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See | |
* {@link FileTransferManager}) | |
* | |
* @param listener the listener to register | |
* @param initiatorJID the JID of the user that wants to establish a SOCKS5 Bytestream | |
*/ | |
public void addIncomingBytestreamListener(BytestreamListener listener, String initiatorJID) { | |
this.userListeners.put(initiatorJID, listener); | |
} | |
/** | |
* Removes the listener for the given user. | |
* | |
* @param initiatorJID the JID of the user the listener should be removed | |
*/ | |
public void removeIncomingBytestreamListener(String initiatorJID) { | |
this.userListeners.remove(initiatorJID); | |
} | |
/** | |
* Use this method to ignore the next incoming SOCKS5 Bytestream request containing the given | |
* session ID. No listeners will be notified for this request and and no error will be returned | |
* to the initiator. | |
* <p> | |
* This method should be used if you are awaiting a SOCKS5 Bytestream request as a reply to | |
* another packet (e.g. file transfer). | |
* | |
* @param sessionID to be ignored | |
*/ | |
public void ignoreBytestreamRequestOnce(String sessionID) { | |
this.ignoredBytestreamRequests.add(sessionID); | |
} | |
/** | |
* Disables the SOCKS5 Bytestream manager by removing the SOCKS5 Bytestream feature from the | |
* service discovery, disabling the listener for SOCKS5 Bytestream initiation requests and | |
* resetting its internal state. | |
* <p> | |
* To re-enable the SOCKS5 Bytestream feature invoke {@link #getBytestreamManager(Connection)}. | |
* Using the file transfer API will automatically re-enable the SOCKS5 Bytestream feature. | |
*/ | |
public synchronized void disableService() { | |
// remove initiation packet listener | |
this.connection.removePacketListener(this.initiationListener); | |
// shutdown threads | |
this.initiationListener.shutdown(); | |
// clear listeners | |
this.allRequestListeners.clear(); | |
this.userListeners.clear(); | |
// reset internal state | |
this.lastWorkingProxy = null; | |
this.proxyBlacklist.clear(); | |
this.ignoredBytestreamRequests.clear(); | |
// remove manager from static managers map | |
managers.remove(this.connection); | |
// shutdown local SOCKS5 proxy if there are no more managers for other connections | |
if (managers.size() == 0) { | |
Socks5Proxy.getSocks5Proxy().stop(); | |
} | |
// remove feature from service discovery | |
ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); | |
// check if service discovery is not already disposed by connection shutdown | |
if (serviceDiscoveryManager != null) { | |
serviceDiscoveryManager.removeFeature(NAMESPACE); | |
} | |
} | |
/** | |
* Returns the timeout to wait for the response to the SOCKS5 Bytestream initialization request. | |
* Default is 10000ms. | |
* | |
* @return the timeout to wait for the response to the SOCKS5 Bytestream initialization request | |
*/ | |
public int getTargetResponseTimeout() { | |
if (this.targetResponseTimeout <= 0) { | |
this.targetResponseTimeout = 10000; | |
} | |
return targetResponseTimeout; | |
} | |
/** | |
* Sets the timeout to wait for the response to the SOCKS5 Bytestream initialization request. | |
* Default is 10000ms. | |
* | |
* @param targetResponseTimeout the timeout to set | |
*/ | |
public void setTargetResponseTimeout(int targetResponseTimeout) { | |
this.targetResponseTimeout = targetResponseTimeout; | |
} | |
/** | |
* Returns the timeout for connecting to the SOCKS5 proxy selected by the target. Default is | |
* 10000ms. | |
* | |
* @return the timeout for connecting to the SOCKS5 proxy selected by the target | |
*/ | |
public int getProxyConnectionTimeout() { | |
if (this.proxyConnectionTimeout <= 0) { | |
this.proxyConnectionTimeout = 10000; | |
} | |
return proxyConnectionTimeout; | |
} | |
/** | |
* Sets the timeout for connecting to the SOCKS5 proxy selected by the target. Default is | |
* 10000ms. | |
* | |
* @param proxyConnectionTimeout the timeout to set | |
*/ | |
public void setProxyConnectionTimeout(int proxyConnectionTimeout) { | |
this.proxyConnectionTimeout = proxyConnectionTimeout; | |
} | |
/** | |
* Returns if the prioritization of the last working SOCKS5 proxy on successive SOCKS5 | |
* Bytestream connections is enabled. Default is <code>true</code>. | |
* | |
* @return <code>true</code> if prioritization is enabled, <code>false</code> otherwise | |
*/ | |
public boolean isProxyPrioritizationEnabled() { | |
return proxyPrioritizationEnabled; | |
} | |
/** | |
* Enable/disable the prioritization of the last working SOCKS5 proxy on successive SOCKS5 | |
* Bytestream connections. | |
* | |
* @param proxyPrioritizationEnabled enable/disable the prioritization of the last working | |
* SOCKS5 proxy | |
*/ | |
public void setProxyPrioritizationEnabled(boolean proxyPrioritizationEnabled) { | |
this.proxyPrioritizationEnabled = proxyPrioritizationEnabled; | |
} | |
/** | |
* Establishes a SOCKS5 Bytestream with the given user and returns the Socket to send/receive | |
* data to/from the user. | |
* <p> | |
* Use this method to establish SOCKS5 Bytestreams to users accepting all incoming Socks5 | |
* bytestream requests since this method doesn't provide a way to tell the user something about | |
* the data to be sent. | |
* <p> | |
* To establish a SOCKS5 Bytestream after negotiation the kind of data to be sent (e.g. file | |
* transfer) use {@link #establishSession(String, String)}. | |
* | |
* @param targetJID the JID of the user a SOCKS5 Bytestream should be established | |
* @return the Socket to send/receive data to/from the user | |
* @throws XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5 | |
* Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies | |
* @throws IOException if the bytestream could not be established | |
* @throws InterruptedException if the current thread was interrupted while waiting | |
*/ | |
public Socks5BytestreamSession establishSession(String targetJID) throws XMPPException, | |
IOException, InterruptedException { | |
String sessionID = getNextSessionID(); | |
return establishSession(targetJID, sessionID); | |
} | |
/** | |
* Establishes a SOCKS5 Bytestream with the given user using the given session ID and returns | |
* the Socket to send/receive data to/from the user. | |
* | |
* @param targetJID the JID of the user a SOCKS5 Bytestream should be established | |
* @param sessionID the session ID for the SOCKS5 Bytestream request | |
* @return the Socket to send/receive data to/from the user | |
* @throws XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5 | |
* Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies | |
* @throws IOException if the bytestream could not be established | |
* @throws InterruptedException if the current thread was interrupted while waiting | |
*/ | |
public Socks5BytestreamSession establishSession(String targetJID, String sessionID) | |
throws XMPPException, IOException, InterruptedException { | |
XMPPException discoveryException = null; | |
// check if target supports SOCKS5 Bytestream | |
if (!supportsSocks5(targetJID)) { | |
throw new XMPPException(targetJID + " doesn't support SOCKS5 Bytestream"); | |
} | |
List<String> proxies = new ArrayList<String>(); | |
// determine SOCKS5 proxies from XMPP-server | |
try { | |
proxies.addAll(determineProxies()); | |
} catch (XMPPException e) { | |
// don't abort here, just remember the exception thrown by determineProxies() | |
// determineStreamHostInfos() will at least add the local Socks5 proxy (if enabled) | |
discoveryException = e; | |
} | |
// determine address and port of each proxy | |
List<StreamHost> streamHosts = determineStreamHostInfos(proxies); | |
if (streamHosts.isEmpty()) { | |
throw discoveryException != null ? discoveryException : new XMPPException("no SOCKS5 proxies available"); | |
} | |
// compute digest | |
String digest = Socks5Utils.createDigest(sessionID, this.connection.getUser(), targetJID); | |
// prioritize last working SOCKS5 proxy if exists | |
if (this.proxyPrioritizationEnabled && this.lastWorkingProxy != null) { | |
StreamHost selectedStreamHost = null; | |
for (StreamHost streamHost : streamHosts) { | |
if (streamHost.getJID().equals(this.lastWorkingProxy)) { | |
selectedStreamHost = streamHost; | |
break; | |
} | |
} | |
if (selectedStreamHost != null) { | |
streamHosts.remove(selectedStreamHost); | |
streamHosts.add(0, selectedStreamHost); | |
} | |
} | |
Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy(); | |
try { | |
// add transfer digest to local proxy to make transfer valid | |
socks5Proxy.addTransfer(digest); | |
// create initiation packet | |
Bytestream initiation = createBytestreamInitiation(sessionID, targetJID, streamHosts); | |
// send initiation packet | |
Packet response = SyncPacketSend.getReply(this.connection, initiation, | |
getTargetResponseTimeout()); | |
// extract used stream host from response | |
StreamHostUsed streamHostUsed = ((Bytestream) response).getUsedHost(); | |
StreamHost usedStreamHost = initiation.getStreamHost(streamHostUsed.getJID()); | |
if (usedStreamHost == null) { | |
throw new XMPPException("Remote user responded with unknown host"); | |
} | |
// build SOCKS5 client | |
Socks5Client socks5Client = new Socks5ClientForInitiator(usedStreamHost, digest, | |
this.connection, sessionID, targetJID); | |
// establish connection to proxy | |
Socket socket = socks5Client.getSocket(getProxyConnectionTimeout()); | |
// remember last working SOCKS5 proxy to prioritize it for next request | |
this.lastWorkingProxy = usedStreamHost.getJID(); | |
// negotiation successful, return the output stream | |
return new Socks5BytestreamSession(socket, usedStreamHost.getJID().equals( | |
this.connection.getUser())); | |
} | |
catch (TimeoutException e) { | |
throw new IOException("Timeout while connecting to SOCKS5 proxy"); | |
} | |
finally { | |
// remove transfer digest if output stream is returned or an exception | |
// occurred | |
socks5Proxy.removeTransfer(digest); | |
} | |
} | |
/** | |
* Returns <code>true</code> if the given target JID supports feature SOCKS5 Bytestream. | |
* | |
* @param targetJID the target JID | |
* @return <code>true</code> if the given target JID supports feature SOCKS5 Bytestream | |
* otherwise <code>false</code> | |
* @throws XMPPException if there was an error querying target for supported features | |
*/ | |
private boolean supportsSocks5(String targetJID) throws XMPPException { | |
ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); | |
DiscoverInfo discoverInfo = serviceDiscoveryManager.discoverInfo(targetJID); | |
return discoverInfo.containsFeature(NAMESPACE); | |
} | |
/** | |
* Returns a list of JIDs of SOCKS5 proxies by querying the XMPP server. The SOCKS5 proxies are | |
* in the same order as returned by the XMPP server. | |
* | |
* @return list of JIDs of SOCKS5 proxies | |
* @throws XMPPException if there was an error querying the XMPP server for SOCKS5 proxies | |
*/ | |
private List<String> determineProxies() throws XMPPException { | |
ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); | |
List<String> proxies = new ArrayList<String>(); | |
// get all items form XMPP server | |
DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(this.connection.getServiceName()); | |
Iterator<Item> itemIterator = discoverItems.getItems(); | |
// query all items if they are SOCKS5 proxies | |
while (itemIterator.hasNext()) { | |
Item item = itemIterator.next(); | |
// skip blacklisted servers | |
if (this.proxyBlacklist.contains(item.getEntityID())) { | |
continue; | |
} | |
try { | |
DiscoverInfo proxyInfo; | |
proxyInfo = serviceDiscoveryManager.discoverInfo(item.getEntityID()); | |
Iterator<Identity> identities = proxyInfo.getIdentities(); | |
// item must have category "proxy" and type "bytestream" | |
while (identities.hasNext()) { | |
Identity identity = identities.next(); | |
if ("proxy".equalsIgnoreCase(identity.getCategory()) | |
&& "bytestreams".equalsIgnoreCase(identity.getType())) { | |
proxies.add(item.getEntityID()); | |
break; | |
} | |
/* | |
* server is not a SOCKS5 proxy, blacklist server to skip next time a Socks5 | |
* bytestream should be established | |
*/ | |
this.proxyBlacklist.add(item.getEntityID()); | |
} | |
} | |
catch (XMPPException e) { | |
// blacklist errornous server | |
this.proxyBlacklist.add(item.getEntityID()); | |
} | |
} | |
return proxies; | |
} | |
/** | |
* Returns a list of stream hosts containing the IP address an the port for the given list of | |
* SOCKS5 proxy JIDs. The order of the returned list is the same as the given list of JIDs | |
* excluding all SOCKS5 proxies who's network settings could not be determined. If a local | |
* SOCKS5 proxy is running it will be the first item in the list returned. | |
* | |
* @param proxies a list of SOCKS5 proxy JIDs | |
* @return a list of stream hosts containing the IP address an the port | |
*/ | |
private List<StreamHost> determineStreamHostInfos(List<String> proxies) { | |
List<StreamHost> streamHosts = new ArrayList<StreamHost>(); | |
// add local proxy on first position if exists | |
List<StreamHost> localProxies = getLocalStreamHost(); | |
if (localProxies != null) { | |
streamHosts.addAll(localProxies); | |
} | |
// query SOCKS5 proxies for network settings | |
for (String proxy : proxies) { | |
Bytestream streamHostRequest = createStreamHostRequest(proxy); | |
try { | |
Bytestream response = (Bytestream) SyncPacketSend.getReply(this.connection, | |
streamHostRequest); | |
streamHosts.addAll(response.getStreamHosts()); | |
} | |
catch (XMPPException e) { | |
// blacklist errornous proxies | |
this.proxyBlacklist.add(proxy); | |
} | |
} | |
return streamHosts; | |
} | |
/** | |
* Returns a IQ packet to query a SOCKS5 proxy its network settings. | |
* | |
* @param proxy the proxy to query | |
* @return IQ packet to query a SOCKS5 proxy its network settings | |
*/ | |
private Bytestream createStreamHostRequest(String proxy) { | |
Bytestream request = new Bytestream(); | |
request.setType(IQ.Type.GET); | |
request.setTo(proxy); | |
return request; | |
} | |
/** | |
* Returns the stream host information of the local SOCKS5 proxy containing the IP address and | |
* the port or null if local SOCKS5 proxy is not running. | |
* | |
* @return the stream host information of the local SOCKS5 proxy or null if local SOCKS5 proxy | |
* is not running | |
*/ | |
private List<StreamHost> getLocalStreamHost() { | |
// get local proxy singleton | |
Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy(); | |
if (socks5Server.isRunning()) { | |
List<String> addresses = socks5Server.getLocalAddresses(); | |
int port = socks5Server.getPort(); | |
if (addresses.size() >= 1) { | |
List<StreamHost> streamHosts = new ArrayList<StreamHost>(); | |
for (String address : addresses) { | |
StreamHost streamHost = new StreamHost(this.connection.getUser(), address); | |
streamHost.setPort(port); | |
streamHosts.add(streamHost); | |
} | |
return streamHosts; | |
} | |
} | |
// server is not running or local address could not be determined | |
return null; | |
} | |
/** | |
* Returns a SOCKS5 Bytestream initialization request packet with the given session ID | |
* containing the given stream hosts for the given target JID. | |
* | |
* @param sessionID the session ID for the SOCKS5 Bytestream | |
* @param targetJID the target JID of SOCKS5 Bytestream request | |
* @param streamHosts a list of SOCKS5 proxies the target should connect to | |
* @return a SOCKS5 Bytestream initialization request packet | |
*/ | |
private Bytestream createBytestreamInitiation(String sessionID, String targetJID, | |
List<StreamHost> streamHosts) { | |
Bytestream initiation = new Bytestream(sessionID); | |
// add all stream hosts | |
for (StreamHost streamHost : streamHosts) { | |
initiation.addStreamHost(streamHost); | |
} | |
initiation.setType(IQ.Type.SET); | |
initiation.setTo(targetJID); | |
return initiation; | |
} | |
/** | |
* Responses to the given packet's sender with a XMPP error that a SOCKS5 Bytestream is not | |
* accepted. | |
* | |
* @param packet Packet that should be answered with a not-acceptable error | |
*/ | |
protected void replyRejectPacket(IQ packet) { | |
XMPPError xmppError = new XMPPError(XMPPError.Condition.no_acceptable); | |
IQ errorIQ = IQ.createErrorResponse(packet, xmppError); | |
this.connection.sendPacket(errorIQ); | |
} | |
/** | |
* Activates the Socks5BytestreamManager by registering the SOCKS5 Bytestream initialization | |
* listener and enabling the SOCKS5 Bytestream feature. | |
*/ | |
private void activate() { | |
// register bytestream initiation packet listener | |
this.connection.addPacketListener(this.initiationListener, | |
this.initiationListener.getFilter()); | |
// enable SOCKS5 feature | |
enableService(); | |
} | |
/** | |
* Adds the SOCKS5 Bytestream feature to the service discovery. | |
*/ | |
private void enableService() { | |
ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(this.connection); | |
if (!manager.includesFeature(NAMESPACE)) { | |
manager.addFeature(NAMESPACE); | |
} | |
} | |
/** | |
* Returns a new unique session ID. | |
* | |
* @return a new unique session ID | |
*/ | |
private String getNextSessionID() { | |
StringBuilder buffer = new StringBuilder(); | |
buffer.append(SESSION_ID_PREFIX); | |
buffer.append(Math.abs(randomGenerator.nextLong())); | |
return buffer.toString(); | |
} | |
/** | |
* Returns the XMPP connection. | |
* | |
* @return the XMPP connection | |
*/ | |
protected Connection getConnection() { | |
return this.connection; | |
} | |
/** | |
* Returns the {@link BytestreamListener} that should be informed if a SOCKS5 Bytestream request | |
* from the given initiator JID is received. | |
* | |
* @param initiator the initiator's JID | |
* @return the listener | |
*/ | |
protected BytestreamListener getUserListener(String initiator) { | |
return this.userListeners.get(initiator); | |
} | |
/** | |
* Returns a list of {@link BytestreamListener} that are informed if there are no listeners for | |
* a specific initiator. | |
* | |
* @return list of listeners | |
*/ | |
protected List<BytestreamListener> getAllRequestListeners() { | |
return this.allRequestListeners; | |
} | |
/** | |
* Returns the list of session IDs that should be ignored by the InitialtionListener | |
* | |
* @return list of session IDs | |
*/ | |
protected List<String> getIgnoredBytestreamRequests() { | |
return ignoredBytestreamRequests; | |
} | |
} |