Socks5Client.java

  1. /**
  2.  *
  3.  * Copyright the original author or authors
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.bytestreams.socks5;

  18. import java.io.DataInputStream;
  19. import java.io.DataOutputStream;
  20. import java.io.IOException;
  21. import java.net.InetSocketAddress;
  22. import java.net.Socket;
  23. import java.net.SocketAddress;
  24. import java.util.Arrays;
  25. import java.util.concurrent.Callable;
  26. import java.util.concurrent.ExecutionException;
  27. import java.util.concurrent.FutureTask;
  28. import java.util.concurrent.TimeUnit;
  29. import java.util.concurrent.TimeoutException;

  30. import org.jivesoftware.smack.SmackException;
  31. import org.jivesoftware.smack.XMPPException;
  32. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  33. import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost;

  34. /**
  35.  * The SOCKS5 client class handles establishing a connection to a SOCKS5 proxy. Connecting to a
  36.  * SOCKS5 proxy requires authentication. This implementation only supports the no-authentication
  37.  * authentication method.
  38.  *
  39.  * @author Henning Staib
  40.  */
  41. class Socks5Client {

  42.     /* stream host containing network settings and name of the SOCKS5 proxy */
  43.     protected StreamHost streamHost;

  44.     /* SHA-1 digest identifying the SOCKS5 stream */
  45.     protected String digest;

  46.     /**
  47.      * Constructor for a SOCKS5 client.
  48.      *
  49.      * @param streamHost containing network settings of the SOCKS5 proxy
  50.      * @param digest identifying the SOCKS5 Bytestream
  51.      */
  52.     public Socks5Client(StreamHost streamHost, String digest) {
  53.         this.streamHost = streamHost;
  54.         this.digest = digest;
  55.     }

  56.     /**
  57.      * Returns the initialized socket that can be used to transfer data between peers via the SOCKS5
  58.      * proxy.
  59.      *
  60.      * @param timeout timeout to connect to SOCKS5 proxy in milliseconds
  61.      * @return socket the initialized socket
  62.      * @throws IOException if initializing the socket failed due to a network error
  63.      * @throws XMPPErrorException if establishing connection to SOCKS5 proxy failed
  64.      * @throws TimeoutException if connecting to SOCKS5 proxy timed out
  65.      * @throws InterruptedException if the current thread was interrupted while waiting
  66.      * @throws SmackException if the connection to the SOC
  67.      * @throws XMPPException
  68.      */
  69.     public Socket getSocket(int timeout) throws IOException, XMPPErrorException, InterruptedException,
  70.                     TimeoutException, SmackException, XMPPException {

  71.         // wrap connecting in future for timeout
  72.         FutureTask<Socket> futureTask = new FutureTask<Socket>(new Callable<Socket>() {

  73.             public Socket call() throws IOException, SmackException {

  74.                 // initialize socket
  75.                 Socket socket = new Socket();
  76.                 SocketAddress socketAddress = new InetSocketAddress(streamHost.getAddress(),
  77.                                 streamHost.getPort());
  78.                 socket.connect(socketAddress);

  79.                 boolean res;
  80.                 // initialize connection to SOCKS5 proxy
  81.                 try {
  82.                     res = establish(socket);
  83.                 }
  84.                 catch (SmackException e) {
  85.                     socket.close();
  86.                     throw e;
  87.                 }

  88.                 if (res) {
  89.                     return socket;
  90.                 }
  91.                 else {
  92.                     socket.close();
  93.                     throw new SmackException("SOCKS5 negotiation failed");
  94.                 }
  95.             }

  96.         });
  97.         Thread executor = new Thread(futureTask);
  98.         executor.start();

  99.         // get connection to initiator with timeout
  100.         try {
  101.             return futureTask.get(timeout, TimeUnit.MILLISECONDS);
  102.         }
  103.         catch (ExecutionException e) {
  104.             Throwable cause = e.getCause();
  105.             if (cause != null) {
  106.                 // case exceptions to comply with method signature
  107.                 if (cause instanceof IOException) {
  108.                     throw (IOException) cause;
  109.                 }
  110.                 if (cause instanceof SmackException) {
  111.                     throw (SmackException) cause;
  112.                 }
  113.             }

  114.             // throw generic IO exception if unexpected exception was thrown
  115.             throw new IOException("Error while connection to SOCKS5 proxy");
  116.         }

  117.     }

  118.     /**
  119.      * Initializes the connection to the SOCKS5 proxy by negotiating authentication method and
  120.      * requesting a stream for the given digest. Currently only the no-authentication method is
  121.      * supported by the Socks5Client.
  122.      * <p>
  123.      * Returns <code>true</code> if a stream could be established, otherwise <code>false</code>. If
  124.      * <code>false</code> is returned the given Socket should be closed.
  125.      *
  126.      * @param socket connected to a SOCKS5 proxy
  127.      * @return <code>true</code> if if a stream could be established, otherwise <code>false</code>.
  128.      *         If <code>false</code> is returned the given Socket should be closed.
  129.      * @throws SmackException
  130.      * @throws IOException
  131.      */
  132.     protected boolean establish(Socket socket) throws SmackException, IOException {

  133.         byte[] connectionRequest;
  134.         byte[] connectionResponse;
  135.         /*
  136.          * use DataInputStream/DataOutpuStream to assure read and write is completed in a single
  137.          * statement
  138.          */
  139.         DataInputStream in = new DataInputStream(socket.getInputStream());
  140.         DataOutputStream out = new DataOutputStream(socket.getOutputStream());

  141.         // authentication negotiation
  142.         byte[] cmd = new byte[3];

  143.         cmd[0] = (byte) 0x05; // protocol version 5
  144.         cmd[1] = (byte) 0x01; // number of authentication methods supported
  145.         cmd[2] = (byte) 0x00; // authentication method: no-authentication required

  146.         out.write(cmd);
  147.         out.flush();

  148.         byte[] response = new byte[2];
  149.         in.readFully(response);

  150.         // check if server responded with correct version and no-authentication method
  151.         if (response[0] != (byte) 0x05 || response[1] != (byte) 0x00) {
  152.             return false;
  153.         }

  154.         // request SOCKS5 connection with given address/digest
  155.         connectionRequest = createSocks5ConnectRequest();
  156.         out.write(connectionRequest);
  157.         out.flush();

  158.         // receive response
  159.         connectionResponse = Socks5Utils.receiveSocks5Message(in);

  160.         // verify response
  161.         connectionRequest[1] = (byte) 0x00; // set expected return status to 0
  162.         return Arrays.equals(connectionRequest, connectionResponse);
  163.     }

  164.     /**
  165.      * Returns a SOCKS5 connection request message. It contains the command "connect", the address
  166.      * type "domain" and the digest as address.
  167.      * <p>
  168.      * (see <a href="http://tools.ietf.org/html/rfc1928">RFC1928</a>)
  169.      *
  170.      * @return SOCKS5 connection request message
  171.      */
  172.     private byte[] createSocks5ConnectRequest() {
  173.         byte[] addr = this.digest.getBytes();

  174.         byte[] data = new byte[7 + addr.length];
  175.         data[0] = (byte) 0x05; // version (SOCKS5)
  176.         data[1] = (byte) 0x01; // command (1 - connect)
  177.         data[2] = (byte) 0x00; // reserved byte (always 0)
  178.         data[3] = (byte) 0x03; // address type (3 - domain name)
  179.         data[4] = (byte) addr.length; // address length
  180.         System.arraycopy(addr, 0, data, 5, addr.length); // address
  181.         data[data.length - 2] = (byte) 0; // address port (2 bytes always 0)
  182.         data[data.length - 1] = (byte) 0;

  183.         return data;
  184.     }

  185. }