2014-02-17 23:58:40 +01:00
/ * *
*
* Copyright the original author or authors
*
* 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.DataInputStream ;
import java.io.DataOutputStream ;
import java.io.IOException ;
2017-02-11 16:16:41 +01:00
import java.io.UnsupportedEncodingException ;
2014-02-17 23:58:40 +01:00
import java.net.InetSocketAddress ;
import java.net.Socket ;
import java.net.SocketAddress ;
import java.util.Arrays ;
import java.util.concurrent.Callable ;
import java.util.concurrent.ExecutionException ;
import java.util.concurrent.FutureTask ;
import java.util.concurrent.TimeUnit ;
import java.util.concurrent.TimeoutException ;
2016-11-05 19:56:34 +01:00
import java.util.logging.Logger ;
2014-02-17 23:58:40 +01:00
2014-03-12 11:50:05 +01:00
import org.jivesoftware.smack.SmackException ;
2019-02-10 21:39:48 +01:00
import org.jivesoftware.smack.SmackException.NoResponseException ;
import org.jivesoftware.smack.SmackException.NotConnectedException ;
import org.jivesoftware.smack.SmackException.SmackMessageException ;
2014-02-17 23:58:40 +01:00
import org.jivesoftware.smack.XMPPException ;
2018-08-15 17:25:22 +02:00
import org.jivesoftware.smack.util.CloseableUtil ;
2017-02-11 16:16:41 +01:00
import org.jivesoftware.smack.util.StringUtils ;
2014-02-17 23:58:40 +01:00
import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost ;
/ * *
* The SOCKS5 client class handles establishing a connection to a SOCKS5 proxy . Connecting to a
* SOCKS5 proxy requires authentication . This implementation only supports the no - authentication
* authentication method .
2018-05-09 23:06:12 +02:00
*
2014-02-17 23:58:40 +01:00
* @author Henning Staib
* /
2017-07-03 10:35:46 +02:00
public class Socks5Client {
2014-02-17 23:58:40 +01:00
2016-11-05 19:56:34 +01:00
private static final Logger LOGGER = Logger . getLogger ( Socks5Client . class . getName ( ) ) ;
2014-02-17 23:58:40 +01:00
/* stream host containing network settings and name of the SOCKS5 proxy */
protected StreamHost streamHost ;
/* SHA-1 digest identifying the SOCKS5 stream */
protected String digest ;
/ * *
* Constructor for a SOCKS5 client .
2018-05-09 23:06:12 +02:00
*
2014-02-17 23:58:40 +01:00
* @param streamHost containing network settings of the SOCKS5 proxy
* @param digest identifying the SOCKS5 Bytestream
* /
public Socks5Client ( StreamHost streamHost , String digest ) {
this . streamHost = streamHost ;
this . digest = digest ;
}
/ * *
* Returns the initialized socket that can be used to transfer data between peers via the SOCKS5
* proxy .
2018-05-09 23:06:12 +02:00
*
2014-02-17 23:58:40 +01:00
* @param timeout timeout to connect to SOCKS5 proxy in milliseconds
* @return socket the initialized socket
* @throws IOException if initializing the socket failed due to a network error
* @throws TimeoutException if connecting to SOCKS5 proxy timed out
* @throws InterruptedException if the current thread was interrupted while waiting
2018-05-09 23:06:12 +02:00
* @throws XMPPException
2019-02-10 21:39:48 +01:00
* @throws SmackMessageException
* @throws NotConnectedException
* @throws NoResponseException
2014-02-17 23:58:40 +01:00
* /
2017-12-13 23:10:11 +01:00
public Socket getSocket ( int timeout ) throws IOException , InterruptedException ,
2019-02-10 21:39:48 +01:00
TimeoutException , XMPPException , SmackMessageException , NotConnectedException , NoResponseException {
2014-02-17 23:58:40 +01:00
// wrap connecting in future for timeout
2017-12-13 23:10:11 +01:00
FutureTask < Socket > futureTask = new FutureTask < > ( new Callable < Socket > ( ) {
2014-02-17 23:58:40 +01:00
2017-02-11 16:16:41 +01:00
@Override
2019-02-10 21:39:48 +01:00
public Socket call ( ) throws IOException , SmackMessageException {
2014-02-17 23:58:40 +01:00
// initialize socket
Socket socket = new Socket ( ) ;
SocketAddress socketAddress = new InetSocketAddress ( streamHost . getAddress ( ) ,
streamHost . getPort ( ) ) ;
socket . connect ( socketAddress ) ;
// initialize connection to SOCKS5 proxy
2014-03-12 11:50:05 +01:00
try {
2016-11-05 19:56:34 +01:00
establish ( socket ) ;
2014-03-12 11:50:05 +01:00
}
2019-02-10 21:39:48 +01:00
catch ( SmackMessageException e ) {
2016-11-05 19:56:34 +01:00
if ( ! socket . isClosed ( ) ) {
2018-08-15 17:25:22 +02:00
CloseableUtil . maybeClose ( socket , LOGGER ) ;
2016-11-05 19:56:34 +01:00
}
2014-03-12 11:50:05 +01:00
throw e ;
2014-02-17 23:58:40 +01:00
}
2016-11-05 19:56:34 +01:00
return socket ;
2014-02-17 23:58:40 +01:00
}
} ) ;
Thread executor = new Thread ( futureTask ) ;
executor . start ( ) ;
// get connection to initiator with timeout
try {
return futureTask . get ( timeout , TimeUnit . MILLISECONDS ) ;
}
catch ( ExecutionException e ) {
Throwable cause = e . getCause ( ) ;
if ( cause ! = null ) {
// case exceptions to comply with method signature
if ( cause instanceof IOException ) {
throw ( IOException ) cause ;
}
2019-02-10 21:39:48 +01:00
if ( cause instanceof SmackMessageException ) {
throw ( SmackMessageException ) cause ;
2014-02-17 23:58:40 +01:00
}
}
2016-02-10 12:36:38 +01:00
// throw generic Smack exception if unexpected exception was thrown
2019-02-10 21:39:48 +01:00
throw new IllegalStateException ( " Error while connecting to SOCKS5 proxy " , e ) ;
2014-02-17 23:58:40 +01:00
}
}
/ * *
* Initializes the connection to the SOCKS5 proxy by negotiating authentication method and
* requesting a stream for the given digest . Currently only the no - authentication method is
* supported by the Socks5Client .
2018-05-09 23:06:12 +02:00
*
2014-02-17 23:58:40 +01:00
* @param socket connected to a SOCKS5 proxy
2018-05-09 23:06:12 +02:00
* @throws IOException
2019-02-10 21:39:48 +01:00
* @throws SmackMessageException
2014-02-17 23:58:40 +01:00
* /
2019-02-10 21:39:48 +01:00
protected void establish ( Socket socket ) throws IOException , SmackMessageException {
2014-02-17 23:58:40 +01:00
2014-03-12 11:50:05 +01:00
byte [ ] connectionRequest ;
byte [ ] connectionResponse ;
2014-02-17 23:58:40 +01:00
/ *
* use DataInputStream / DataOutpuStream to assure read and write is completed in a single
* statement
* /
2014-03-17 21:06:45 +01:00
DataInputStream in = new DataInputStream ( socket . getInputStream ( ) ) ;
DataOutputStream out = new DataOutputStream ( socket . getOutputStream ( ) ) ;
2014-02-17 23:58:40 +01:00
2014-03-17 21:06:45 +01:00
// authentication negotiation
byte [ ] cmd = new byte [ 3 ] ;
2014-02-17 23:58:40 +01:00
2014-03-17 21:06:45 +01:00
cmd [ 0 ] = ( byte ) 0x05 ; // protocol version 5
cmd [ 1 ] = ( byte ) 0x01 ; // number of authentication methods supported
cmd [ 2 ] = ( byte ) 0x00 ; // authentication method: no-authentication required
2014-02-17 23:58:40 +01:00
2014-03-17 21:06:45 +01:00
out . write ( cmd ) ;
out . flush ( ) ;
2014-02-17 23:58:40 +01:00
2014-03-17 21:06:45 +01:00
byte [ ] response = new byte [ 2 ] ;
in . readFully ( response ) ;
2014-02-17 23:58:40 +01:00
2014-03-17 21:06:45 +01:00
// check if server responded with correct version and no-authentication method
if ( response [ 0 ] ! = ( byte ) 0x05 | | response [ 1 ] ! = ( byte ) 0x00 ) {
2019-02-10 21:39:48 +01:00
throw new SmackException . SmackMessageException ( " Remote SOCKS5 server responded with unexpected version: " + response [ 0 ] + ' ' + response [ 1 ] + " . Should be 0x05 0x00. " ) ;
2014-03-17 21:06:45 +01:00
}
2014-02-17 23:58:40 +01:00
2014-03-17 21:06:45 +01:00
// request SOCKS5 connection with given address/digest
connectionRequest = createSocks5ConnectRequest ( ) ;
out . write ( connectionRequest ) ;
out . flush ( ) ;
// receive response
connectionResponse = Socks5Utils . receiveSocks5Message ( in ) ;
2014-02-17 23:58:40 +01:00
// verify response
connectionRequest [ 1 ] = ( byte ) 0x00 ; // set expected return status to 0
2016-11-05 19:56:34 +01:00
if ( ! Arrays . equals ( connectionRequest , connectionResponse ) ) {
2019-02-10 21:39:48 +01:00
throw new SmackException . SmackMessageException (
2017-12-13 23:10:11 +01:00
" Connection request does not equal connection response. Response: "
2016-11-05 19:56:34 +01:00
+ Arrays . toString ( connectionResponse ) + " . Request: "
+ Arrays . toString ( connectionRequest ) ) ;
}
2014-02-17 23:58:40 +01:00
}
/ * *
* Returns a SOCKS5 connection request message . It contains the command " connect " , the address
* type " domain " and the digest as address .
* < p >
* ( see < a href = " http://tools.ietf.org/html/rfc1928 " > RFC1928 < / a > )
2018-05-09 23:06:12 +02:00
*
2014-02-17 23:58:40 +01:00
* @return SOCKS5 connection request message
* /
private byte [ ] createSocks5ConnectRequest ( ) {
2017-02-11 16:16:41 +01:00
byte [ ] addr ;
try {
addr = digest . getBytes ( StringUtils . UTF8 ) ;
}
catch ( UnsupportedEncodingException e ) {
throw new AssertionError ( e ) ;
}
2014-02-17 23:58:40 +01:00
byte [ ] data = new byte [ 7 + addr . length ] ;
data [ 0 ] = ( byte ) 0x05 ; // version (SOCKS5)
data [ 1 ] = ( byte ) 0x01 ; // command (1 - connect)
data [ 2 ] = ( byte ) 0x00 ; // reserved byte (always 0)
data [ 3 ] = ( byte ) 0x03 ; // address type (3 - domain name)
data [ 4 ] = ( byte ) addr . length ; // address length
System . arraycopy ( addr , 0 , data , 5 , addr . length ) ; // address
data [ data . length - 2 ] = ( byte ) 0 ; // address port (2 bytes always 0)
data [ data . length - 1 ] = ( byte ) 0 ;
return data ;
}
}