• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.net.Socket;
18 import java.util.Collection;
19 import java.util.concurrent.TimeoutException;
20 
21 import org.jivesoftware.smack.XMPPException;
22 import org.jivesoftware.smack.packet.IQ;
23 import org.jivesoftware.smack.packet.XMPPError;
24 import org.jivesoftware.smack.util.Cache;
25 import org.jivesoftware.smackx.bytestreams.BytestreamRequest;
26 import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
27 import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost;
28 
29 /**
30  * Socks5BytestreamRequest class handles incoming SOCKS5 Bytestream requests.
31  *
32  * @author Henning Staib
33  */
34 public class Socks5BytestreamRequest implements BytestreamRequest {
35 
36     /* lifetime of an Item in the blacklist */
37     private static final long BLACKLIST_LIFETIME = 60 * 1000 * 120;
38 
39     /* size of the blacklist */
40     private static final int BLACKLIST_MAX_SIZE = 100;
41 
42     /* blacklist of addresses of SOCKS5 proxies */
43     private static final Cache<String, Integer> ADDRESS_BLACKLIST = new Cache<String, Integer>(
44                     BLACKLIST_MAX_SIZE, BLACKLIST_LIFETIME);
45 
46     /*
47      * The number of connection failures it takes for a particular SOCKS5 proxy to be blacklisted.
48      * When a proxy is blacklisted no more connection attempts will be made to it for a period of 2
49      * hours.
50      */
51     private static int CONNECTION_FAILURE_THRESHOLD = 2;
52 
53     /* the bytestream initialization request */
54     private Bytestream bytestreamRequest;
55 
56     /* SOCKS5 Bytestream manager containing the XMPP connection and helper methods */
57     private Socks5BytestreamManager manager;
58 
59     /* timeout to connect to all SOCKS5 proxies */
60     private int totalConnectTimeout = 10000;
61 
62     /* minimum timeout to connect to one SOCKS5 proxy */
63     private int minimumConnectTimeout = 2000;
64 
65     /**
66      * Returns the number of connection failures it takes for a particular SOCKS5 proxy to be
67      * blacklisted. When a proxy is blacklisted no more connection attempts will be made to it for a
68      * period of 2 hours. Default is 2.
69      *
70      * @return the number of connection failures it takes for a particular SOCKS5 proxy to be
71      *         blacklisted
72      */
getConnectFailureThreshold()73     public static int getConnectFailureThreshold() {
74         return CONNECTION_FAILURE_THRESHOLD;
75     }
76 
77     /**
78      * Sets the number of connection failures it takes for a particular SOCKS5 proxy to be
79      * blacklisted. When a proxy is blacklisted no more connection attempts will be made to it for a
80      * period of 2 hours. Default is 2.
81      * <p>
82      * Setting the connection failure threshold to zero disables the blacklisting.
83      *
84      * @param connectFailureThreshold the number of connection failures it takes for a particular
85      *        SOCKS5 proxy to be blacklisted
86      */
setConnectFailureThreshold(int connectFailureThreshold)87     public static void setConnectFailureThreshold(int connectFailureThreshold) {
88         CONNECTION_FAILURE_THRESHOLD = connectFailureThreshold;
89     }
90 
91     /**
92      * Creates a new Socks5BytestreamRequest.
93      *
94      * @param manager the SOCKS5 Bytestream manager
95      * @param bytestreamRequest the SOCKS5 Bytestream initialization packet
96      */
Socks5BytestreamRequest(Socks5BytestreamManager manager, Bytestream bytestreamRequest)97     protected Socks5BytestreamRequest(Socks5BytestreamManager manager, Bytestream bytestreamRequest) {
98         this.manager = manager;
99         this.bytestreamRequest = bytestreamRequest;
100     }
101 
102     /**
103      * Returns the maximum timeout to connect to SOCKS5 proxies. Default is 10000ms.
104      * <p>
105      * When accepting a SOCKS5 Bytestream request Smack tries to connect to all SOCKS5 proxies given
106      * by the initiator until a connection is established. This timeout divided by the number of
107      * SOCKS5 proxies determines the timeout for every connection attempt.
108      * <p>
109      * You can set the minimum timeout for establishing a connection to one SOCKS5 proxy by invoking
110      * {@link #setMinimumConnectTimeout(int)}.
111      *
112      * @return the maximum timeout to connect to SOCKS5 proxies
113      */
getTotalConnectTimeout()114     public int getTotalConnectTimeout() {
115         if (this.totalConnectTimeout <= 0) {
116             return 10000;
117         }
118         return this.totalConnectTimeout;
119     }
120 
121     /**
122      * Sets the maximum timeout to connect to SOCKS5 proxies. Default is 10000ms.
123      * <p>
124      * When accepting a SOCKS5 Bytestream request Smack tries to connect to all SOCKS5 proxies given
125      * by the initiator until a connection is established. This timeout divided by the number of
126      * SOCKS5 proxies determines the timeout for every connection attempt.
127      * <p>
128      * You can set the minimum timeout for establishing a connection to one SOCKS5 proxy by invoking
129      * {@link #setMinimumConnectTimeout(int)}.
130      *
131      * @param totalConnectTimeout the maximum timeout to connect to SOCKS5 proxies
132      */
setTotalConnectTimeout(int totalConnectTimeout)133     public void setTotalConnectTimeout(int totalConnectTimeout) {
134         this.totalConnectTimeout = totalConnectTimeout;
135     }
136 
137     /**
138      * Returns the timeout to connect to one SOCKS5 proxy while accepting the SOCKS5 Bytestream
139      * request. Default is 2000ms.
140      *
141      * @return the timeout to connect to one SOCKS5 proxy
142      */
getMinimumConnectTimeout()143     public int getMinimumConnectTimeout() {
144         if (this.minimumConnectTimeout <= 0) {
145             return 2000;
146         }
147         return this.minimumConnectTimeout;
148     }
149 
150     /**
151      * Sets the timeout to connect to one SOCKS5 proxy while accepting the SOCKS5 Bytestream
152      * request. Default is 2000ms.
153      *
154      * @param minimumConnectTimeout the timeout to connect to one SOCKS5 proxy
155      */
setMinimumConnectTimeout(int minimumConnectTimeout)156     public void setMinimumConnectTimeout(int minimumConnectTimeout) {
157         this.minimumConnectTimeout = minimumConnectTimeout;
158     }
159 
160     /**
161      * Returns the sender of the SOCKS5 Bytestream initialization request.
162      *
163      * @return the sender of the SOCKS5 Bytestream initialization request.
164      */
getFrom()165     public String getFrom() {
166         return this.bytestreamRequest.getFrom();
167     }
168 
169     /**
170      * Returns the session ID of the SOCKS5 Bytestream initialization request.
171      *
172      * @return the session ID of the SOCKS5 Bytestream initialization request.
173      */
getSessionID()174     public String getSessionID() {
175         return this.bytestreamRequest.getSessionID();
176     }
177 
178     /**
179      * Accepts the SOCKS5 Bytestream initialization request and returns the socket to send/receive
180      * data.
181      * <p>
182      * Before accepting the SOCKS5 Bytestream request you can set timeouts by invoking
183      * {@link #setTotalConnectTimeout(int)} and {@link #setMinimumConnectTimeout(int)}.
184      *
185      * @return the socket to send/receive data
186      * @throws XMPPException if connection to all SOCKS5 proxies failed or if stream is invalid.
187      * @throws InterruptedException if the current thread was interrupted while waiting
188      */
accept()189     public Socks5BytestreamSession accept() throws XMPPException, InterruptedException {
190         Collection<StreamHost> streamHosts = this.bytestreamRequest.getStreamHosts();
191 
192         // throw exceptions if request contains no stream hosts
193         if (streamHosts.size() == 0) {
194             cancelRequest();
195         }
196 
197         StreamHost selectedHost = null;
198         Socket socket = null;
199 
200         String digest = Socks5Utils.createDigest(this.bytestreamRequest.getSessionID(),
201                         this.bytestreamRequest.getFrom(), this.manager.getConnection().getUser());
202 
203         /*
204          * determine timeout for each connection attempt; each SOCKS5 proxy has the same amount of
205          * time so that the first does not consume the whole timeout
206          */
207         int timeout = Math.max(getTotalConnectTimeout() / streamHosts.size(),
208                         getMinimumConnectTimeout());
209 
210         for (StreamHost streamHost : streamHosts) {
211             String address = streamHost.getAddress() + ":" + streamHost.getPort();
212 
213             // check to see if this address has been blacklisted
214             int failures = getConnectionFailures(address);
215             if (CONNECTION_FAILURE_THRESHOLD > 0 && failures >= CONNECTION_FAILURE_THRESHOLD) {
216                 continue;
217             }
218 
219             // establish socket
220             try {
221 
222                 // build SOCKS5 client
223                 final Socks5Client socks5Client = new Socks5Client(streamHost, digest);
224 
225                 // connect to SOCKS5 proxy with a timeout
226                 socket = socks5Client.getSocket(timeout);
227 
228                 // set selected host
229                 selectedHost = streamHost;
230                 break;
231 
232             }
233             catch (TimeoutException e) {
234                 incrementConnectionFailures(address);
235             }
236             catch (IOException e) {
237                 incrementConnectionFailures(address);
238             }
239             catch (XMPPException e) {
240                 incrementConnectionFailures(address);
241             }
242 
243         }
244 
245         // throw exception if connecting to all SOCKS5 proxies failed
246         if (selectedHost == null || socket == null) {
247             cancelRequest();
248         }
249 
250         // send used-host confirmation
251         Bytestream response = createUsedHostResponse(selectedHost);
252         this.manager.getConnection().sendPacket(response);
253 
254         return new Socks5BytestreamSession(socket, selectedHost.getJID().equals(
255                         this.bytestreamRequest.getFrom()));
256 
257     }
258 
259     /**
260      * Rejects the SOCKS5 Bytestream request by sending a reject error to the initiator.
261      */
reject()262     public void reject() {
263         this.manager.replyRejectPacket(this.bytestreamRequest);
264     }
265 
266     /**
267      * Cancels the SOCKS5 Bytestream request by sending an error to the initiator and building a
268      * XMPP exception.
269      *
270      * @throws XMPPException XMPP exception containing the XMPP error
271      */
cancelRequest()272     private void cancelRequest() throws XMPPException {
273         String errorMessage = "Could not establish socket with any provided host";
274         XMPPError error = new XMPPError(XMPPError.Condition.item_not_found, errorMessage);
275         IQ errorIQ = IQ.createErrorResponse(this.bytestreamRequest, error);
276         this.manager.getConnection().sendPacket(errorIQ);
277         throw new XMPPException(errorMessage, error);
278     }
279 
280     /**
281      * Returns the response to the SOCKS5 Bytestream request containing the SOCKS5 proxy used.
282      *
283      * @param selectedHost the used SOCKS5 proxy
284      * @return the response to the SOCKS5 Bytestream request
285      */
createUsedHostResponse(StreamHost selectedHost)286     private Bytestream createUsedHostResponse(StreamHost selectedHost) {
287         Bytestream response = new Bytestream(this.bytestreamRequest.getSessionID());
288         response.setTo(this.bytestreamRequest.getFrom());
289         response.setType(IQ.Type.RESULT);
290         response.setPacketID(this.bytestreamRequest.getPacketID());
291         response.setUsedHost(selectedHost.getJID());
292         return response;
293     }
294 
295     /**
296      * Increments the connection failure counter by one for the given address.
297      *
298      * @param address the address the connection failure counter should be increased
299      */
incrementConnectionFailures(String address)300     private void incrementConnectionFailures(String address) {
301         Integer count = ADDRESS_BLACKLIST.get(address);
302         ADDRESS_BLACKLIST.put(address, count == null ? 1 : count + 1);
303     }
304 
305     /**
306      * Returns how often the connection to the given address failed.
307      *
308      * @param address the address
309      * @return number of connection failures
310      */
getConnectionFailures(String address)311     private int getConnectionFailures(String address) {
312         Integer count = ADDRESS_BLACKLIST.get(address);
313         return count != null ? count : 0;
314     }
315 
316 }
317