blob: 138349590a69900e0d6ada0d147b43d29db1fb7d [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
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 */
14package org.jivesoftware.smackx.bytestreams.socks5;
15
16import java.io.IOException;
17import java.lang.ref.WeakReference;
18import java.net.Socket;
19import java.util.ArrayList;
20import java.util.Collections;
21import java.util.Iterator;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.Map;
25import java.util.Random;
26import java.util.WeakHashMap;
27import java.util.concurrent.ConcurrentHashMap;
28import java.util.concurrent.TimeoutException;
29
30import org.jivesoftware.smack.AbstractConnectionListener;
31import org.jivesoftware.smack.Connection;
32import org.jivesoftware.smack.ConnectionCreationListener;
33import org.jivesoftware.smack.XMPPException;
34import org.jivesoftware.smack.packet.IQ;
35import org.jivesoftware.smack.packet.Packet;
36import org.jivesoftware.smack.packet.XMPPError;
37import org.jivesoftware.smack.util.SyncPacketSend;
38import org.jivesoftware.smackx.ServiceDiscoveryManager;
39import org.jivesoftware.smackx.bytestreams.BytestreamListener;
40import org.jivesoftware.smackx.bytestreams.BytestreamManager;
41import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
42import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost;
43import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHostUsed;
44import org.jivesoftware.smackx.filetransfer.FileTransferManager;
45import org.jivesoftware.smackx.packet.DiscoverInfo;
46import org.jivesoftware.smackx.packet.DiscoverItems;
47import org.jivesoftware.smackx.packet.DiscoverInfo.Identity;
48import 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 */
84public 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}