Socks5Proxy.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.InetAddress;
  22. import java.net.InterfaceAddress;
  23. import java.net.NetworkInterface;
  24. import java.net.ServerSocket;
  25. import java.net.Socket;
  26. import java.net.SocketException;
  27. import java.nio.charset.StandardCharsets;
  28. import java.util.Collection;
  29. import java.util.Collections;
  30. import java.util.Enumeration;
  31. import java.util.HashSet;
  32. import java.util.LinkedHashSet;
  33. import java.util.LinkedList;
  34. import java.util.List;
  35. import java.util.Map;
  36. import java.util.Set;
  37. import java.util.concurrent.ConcurrentHashMap;
  38. import java.util.concurrent.CopyOnWriteArrayList;
  39. import java.util.logging.Level;
  40. import java.util.logging.Logger;

  41. import org.jivesoftware.smack.SmackException;
  42. import org.jivesoftware.smack.util.CloseableUtil;

  43. /**
  44.  * The Socks5Proxy class represents a local SOCKS5 proxy server. It can be enabled/disabled by
  45.  * invoking {@link #setLocalSocks5ProxyEnabled(boolean)}. The proxy is enabled by default.
  46.  * <p>
  47.  * The port of the local SOCKS5 proxy can be configured by invoking
  48.  * {@link #setLocalSocks5ProxyPort(int)}. Default port is 7777. If you set the port to a negative
  49.  * value Smack tries to the absolute value and all following until it finds an open port.
  50.  * <p>
  51.  * If your application is running on a machine with multiple network interfaces or if you want to
  52.  * provide your public address in case you are behind a NAT router, invoke
  53.  * {@link #addLocalAddress(InetAddress)} or {@link #replaceLocalAddresses(Collection)} to modify the list of
  54.  * local network addresses used for outgoing SOCKS5 Bytestream requests.
  55.  * <p>
  56.  * The local SOCKS5 proxy server refuses all connections except the ones that are explicitly allowed
  57.  * in the process of establishing a SOCKS5 Bytestream (
  58.  * {@link Socks5BytestreamManager#establishSession(org.jxmpp.jid.Jid)}).
  59.  * <p>
  60.  * This Implementation has the following limitations:
  61.  * <ul>
  62.  * <li>only supports the no-authentication authentication method</li>
  63.  * <li>only supports the <code>connect</code> command and will not answer correctly to other
  64.  * commands</li>
  65.  * <li>only supports requests with the domain address type and will not correctly answer to requests
  66.  * with other address types</li>
  67.  * </ul>
  68.  * (see <a href="http://tools.ietf.org/html/rfc1928">RFC 1928</a>)
  69.  *
  70.  * @author Henning Staib
  71.  */
  72. public class Socks5Proxy {
  73.     private static final Logger LOGGER = Logger.getLogger(Socks5Proxy.class.getName());

  74.     private static final List<Socks5Proxy> RUNNING_PROXIES = new CopyOnWriteArrayList<>();

  75.     /* SOCKS5 proxy singleton */
  76.     private static Socks5Proxy socks5Server;

  77.     private static boolean localSocks5ProxyEnabled = true;

  78.     /**
  79.      * The default port of the local Socks5 Proxy. If this value is negative, the next ports will be tried
  80.      * until a unused is found.
  81.      */
  82.     private static int DEFAULT_LOCAL_SOCKS5_PROXY_PORT = -7777;

  83.     /**
  84.      * The port of the local Socks5 Proxy. If this value is negative, the next ports will be tried
  85.      * until a unused is found.
  86.      */
  87.     private int localSocks5ProxyPort = -7777;

  88.     /* reusable implementation of a SOCKS5 proxy server process */
  89.     private final Socks5ServerProcess serverProcess;

  90.     /* thread running the SOCKS5 server process */
  91.     private Thread serverThread;

  92.     /* server socket to accept SOCKS5 connections */
  93.     private ServerSocket serverSocket;

  94.     /* assigns a connection to a digest */
  95.     private final Map<String, Socket> connectionMap = new ConcurrentHashMap<>();

  96.     /* list of digests connections should be stored */
  97.     private final List<String> allowedConnections = Collections.synchronizedList(new LinkedList<String>());

  98.     private final Set<InetAddress> localAddresses = new LinkedHashSet<>(4);

  99.     /**
  100.      * If set to <code>true</code>, then all connections are allowed and the digest is not verified. Should be set to
  101.      * <code>false</code> for production usage and <code>true</code> for (unit) testing purposes.
  102.      */
  103.     private final boolean allowAllConnections;

  104.     /**
  105.      * Private constructor.
  106.      */
  107.     Socks5Proxy() {
  108.         this.serverProcess = new Socks5ServerProcess();

  109.         allowAllConnections = false;

  110.         Enumeration<NetworkInterface> networkInterfaces;
  111.         try {
  112.             networkInterfaces = NetworkInterface.getNetworkInterfaces();
  113.         } catch (SocketException e) {
  114.             throw new IllegalStateException(e);
  115.         }
  116.         Set<InetAddress> localAddresses = new HashSet<>();
  117.         for (NetworkInterface networkInterface : Collections.list(networkInterfaces)) {
  118.             List<InterfaceAddress> interfaceAddresses = networkInterface.getInterfaceAddresses();
  119.             for (InterfaceAddress interfaceAddress : interfaceAddresses) {
  120.                 localAddresses.add(interfaceAddress.getAddress());
  121.             }
  122.         }
  123.         if (localAddresses.isEmpty()) {
  124.             throw new IllegalStateException("Could not determine any local internet address");
  125.         }
  126.         replaceLocalAddresses(localAddresses);
  127.     }

  128.     /**
  129.      * Constructor a Socks5Proxy with the given socket. Used for unit test purposes.
  130.      *
  131.      * @param serverSocket the server socket to use
  132.      */
  133.     protected Socks5Proxy(ServerSocket serverSocket) {
  134.         this.serverProcess = new Socks5ServerProcess();
  135.         this.serverSocket = serverSocket;

  136.         allowAllConnections = true;

  137.         startServerThread();
  138.     }



  139.    /**
  140.     * Returns true if the local Socks5 proxy should be started. Default is true.
  141.     *
  142.     * @return if the local Socks5 proxy should be started
  143.     */
  144.    public static boolean isLocalSocks5ProxyEnabled() {
  145.        return localSocks5ProxyEnabled;
  146.    }

  147.    /**
  148.     * Sets if the local Socks5 proxy should be started. Default is true.
  149.     *
  150.     * @param localSocks5ProxyEnabled if the local Socks5 proxy should be started
  151.     */
  152.    public static void setLocalSocks5ProxyEnabled(boolean localSocks5ProxyEnabled) {
  153.        Socks5Proxy.localSocks5ProxyEnabled = localSocks5ProxyEnabled;
  154.    }

  155.    private static void checkLocalSocks5ProxyPortArgument(int port) {
  156.        if (Math.abs(port) > 65535) {
  157.            throw new IllegalArgumentException("Local SOCKS5 proxy port must be within (-65535,65535)");
  158.        }
  159.    }

  160.    public static int getDefaultLocalSocks5ProxyPort() {
  161.        return DEFAULT_LOCAL_SOCKS5_PROXY_PORT;
  162.    }

  163.    public static void setDefaultLocalSocsk5ProxyPort(int defaultLocalSocks5ProxyPort) {
  164.        checkLocalSocks5ProxyPortArgument(defaultLocalSocks5ProxyPort);
  165.        DEFAULT_LOCAL_SOCKS5_PROXY_PORT = defaultLocalSocks5ProxyPort;
  166.    }

  167.    /**
  168.     * Return the port of the local Socks5 proxy. Default is 7777.
  169.     *
  170.     * @return the port of the local Socks5 proxy
  171.     */
  172.    public int getLocalSocks5ProxyPort() {
  173.        return localSocks5ProxyPort;
  174.    }

  175.    /**
  176.     * Sets the port of the local Socks5 proxy. Default is 7777. If you set the port to a negative
  177.     * value Smack tries the absolute value and all following until it finds an open port.
  178.     *
  179.     * @param localSocks5ProxyPort the port of the local Socks5 proxy to set
  180.     */
  181.    public void setLocalSocks5ProxyPort(int localSocks5ProxyPort) {
  182.        checkLocalSocks5ProxyPortArgument(localSocks5ProxyPort);
  183.        this.localSocks5ProxyPort = localSocks5ProxyPort;
  184.    }

  185.     /**
  186.      * Returns the local SOCKS5 proxy server.
  187.      *
  188.      * @return the local SOCKS5 proxy server
  189.      */
  190.     public static synchronized Socks5Proxy getSocks5Proxy() {
  191.         if (socks5Server == null) {
  192.             socks5Server = new Socks5Proxy();
  193.         }
  194.         if (isLocalSocks5ProxyEnabled()) {
  195.             socks5Server.start();
  196.         }
  197.         return socks5Server;
  198.     }

  199.     /**
  200.      * Starts the local SOCKS5 proxy server. If it is already running, this method does nothing.
  201.      *
  202.      * @return the server socket.
  203.      */
  204.     public synchronized ServerSocket start() {
  205.         if (isRunning()) {
  206.             return this.serverSocket;
  207.         }
  208.         try {
  209.             if (getLocalSocks5ProxyPort() < 0) {
  210.                 int port = Math.abs(getLocalSocks5ProxyPort());
  211.                 for (int i = 0; i < 65535 - port; i++) {
  212.                     try {
  213.                         this.serverSocket = new ServerSocket(port + i);
  214.                         break;
  215.                     }
  216.                     catch (IOException e) {
  217.                         // port is used, try next one
  218.                     }
  219.                 }
  220.             }
  221.             else {
  222.                 this.serverSocket = new ServerSocket(getLocalSocks5ProxyPort());
  223.             }

  224.             if (this.serverSocket != null) {
  225.                 startServerThread();
  226.             }
  227.         }
  228.         catch (IOException e) {
  229.             // couldn't setup server
  230.             LOGGER.log(Level.SEVERE, "couldn't setup local SOCKS5 proxy on port " + getLocalSocks5ProxyPort(), e);
  231.         }

  232.         return this.serverSocket;
  233.     }

  234.     private synchronized void startServerThread() {
  235.         this.serverThread = new Thread(this.serverProcess);
  236.         this.serverThread.setName("Smack Local SOCKS5 Proxy [" + this.serverSocket + ']');
  237.         this.serverThread.setDaemon(true);

  238.         RUNNING_PROXIES.add(this);
  239.         this.serverThread.start();
  240.     }

  241.     /**
  242.      * Stops the local SOCKS5 proxy server. If it is not running this method does nothing.
  243.      */
  244.     public synchronized void stop() {
  245.         if (!isRunning()) {
  246.             return;
  247.         }

  248.         RUNNING_PROXIES.remove(this);

  249.         CloseableUtil.maybeClose(this.serverSocket, LOGGER);

  250.         if (this.serverThread != null && this.serverThread.isAlive()) {
  251.             try {
  252.                 this.serverThread.interrupt();
  253.                 this.serverThread.join();
  254.             }
  255.             catch (InterruptedException e) {
  256.                 LOGGER.log(Level.WARNING, "SOCKS5 server thread termination was interrupted", e);
  257.             }
  258.         }
  259.         this.serverThread = null;
  260.         this.serverSocket = null;
  261.     }

  262.     /**
  263.      * Adds the given address to the list of local network addresses.
  264.      * <p>
  265.      * Use this method if you want to provide multiple addresses in a SOCKS5 Bytestream request.
  266.      * This may be necessary if your application is running on a machine with multiple network
  267.      * interfaces or if you want to provide your public address in case you are behind a NAT router.
  268.      * <p>
  269.      * The order of the addresses used is determined by the order you add addresses.
  270.      * <p>
  271.      * Note that the list of addresses initially contains the address returned by
  272.      * <code>InetAddress.getLocalHost().getHostAddress()</code>. You can replace the list of
  273.      * addresses by invoking {@link #replaceLocalAddresses(Collection)}.
  274.      *
  275.      * @param address the local network address to add
  276.      */
  277.     public void addLocalAddress(InetAddress address) {
  278.         if (address == null) {
  279.             return;
  280.         }
  281.         synchronized (localAddresses) {
  282.             this.localAddresses.add(address);
  283.         }
  284.     }

  285.     /**
  286.      * Removes the given address from the list of local network addresses. This address will then no
  287.      * longer be used of outgoing SOCKS5 Bytestream requests.
  288.      *
  289.      * @param address the local network address to remove
  290.      * @return true if the address was removed.
  291.      */
  292.     public boolean removeLocalAddress(InetAddress address) {
  293.         synchronized (localAddresses) {
  294.             return localAddresses.remove(address);
  295.         }
  296.     }

  297.     /**
  298.      * Returns an set of the local network addresses that will be used for streamhost
  299.      * candidates of outgoing SOCKS5 Bytestream requests.
  300.      *
  301.      * @return set of the local network addresses
  302.      */
  303.     public List<InetAddress> getLocalAddresses() {
  304.         synchronized (localAddresses) {
  305.             return new LinkedList<>(localAddresses);
  306.         }
  307.     }

  308.     /**
  309.      * Replaces the list of local network addresses.
  310.      * <p>
  311.      * Use this method if you want to provide multiple addresses in a SOCKS5 Bytestream request and
  312.      * want to define their order. This may be necessary if your application is running on a machine
  313.      * with multiple network interfaces or if you want to provide your public address in case you
  314.      * are behind a NAT router.
  315.      *
  316.      * @param addresses the new list of local network addresses
  317.      */
  318.     public void replaceLocalAddresses(Collection<? extends InetAddress> addresses) {
  319.         if (addresses == null) {
  320.             throw new IllegalArgumentException("list must not be null");
  321.         }
  322.         synchronized (localAddresses) {
  323.             localAddresses.clear();
  324.             localAddresses.addAll(addresses);
  325.         }
  326.     }

  327.     /**
  328.      * Returns the port of the local SOCKS5 proxy server. If it is not running -1 will be returned.
  329.      *
  330.      * @return the port of the local SOCKS5 proxy server or -1 if proxy is not running
  331.      */
  332.     public int getPort() {
  333.         if (!isRunning()) {
  334.             return -1;
  335.         }
  336.         return this.serverSocket.getLocalPort();
  337.     }

  338.     /**
  339.      * Returns the socket for the given digest. A socket will be returned if the given digest has
  340.      * been in the list of allowed transfers (see {@link #addTransfer(String)}) while the peer
  341.      * connected to the SOCKS5 proxy.
  342.      *
  343.      * @param digest identifying the connection
  344.      * @return socket or null if there is no socket for the given digest
  345.      */
  346.     protected Socket getSocket(String digest) {
  347.         return this.connectionMap.get(digest);
  348.     }

  349.     /**
  350.      * Add the given digest to the list of allowed transfers. Only connections for allowed transfers
  351.      * are stored and can be retrieved by invoking {@link #getSocket(String)}. All connections to
  352.      * the local SOCKS5 proxy that don't contain an allowed digest are discarded.
  353.      *
  354.      * @param digest to be added to the list of allowed transfers
  355.      */
  356.     public void addTransfer(String digest) {
  357.         this.allowedConnections.add(digest);
  358.     }

  359.     /**
  360.      * Removes the given digest from the list of allowed transfers. After invoking this method
  361.      * already stored connections with the given digest will be removed.
  362.      * <p>
  363.      * The digest should be removed after establishing the SOCKS5 Bytestream is finished, an error
  364.      * occurred while establishing the connection or if the connection is not allowed anymore.
  365.      *
  366.      * @param digest to be removed from the list of allowed transfers
  367.      */
  368.     protected void removeTransfer(String digest) {
  369.         this.allowedConnections.remove(digest);
  370.         this.connectionMap.remove(digest);
  371.     }

  372.     /**
  373.      * Returns <code>true</code> if the local SOCKS5 proxy server is running, otherwise
  374.      * <code>false</code>.
  375.      *
  376.      * @return <code>true</code> if the local SOCKS5 proxy server is running, otherwise
  377.      *         <code>false</code>
  378.      */
  379.     public boolean isRunning() {
  380.         return this.serverSocket != null;
  381.     }

  382.     /**
  383.      * Implementation of a simplified SOCKS5 proxy server.
  384.      */
  385.     private class Socks5ServerProcess implements Runnable {

  386.         @Override
  387.         public void run() {
  388.             while (true) {
  389.                 ServerSocket serverSocket = Socks5Proxy.this.serverSocket;
  390.                 if (serverSocket == null || serverSocket.isClosed() || Thread.currentThread().isInterrupted()) {
  391.                     return;
  392.                 }

  393.                 // accept connection
  394.                 Socket socket = null;
  395.                 try {
  396.                     socket = serverSocket.accept();
  397.                     // initialize connection
  398.                     establishConnection(socket);
  399.                 } catch (SmackException | IOException e) {
  400.                     // Do nothing, if caused by closing the server socket, thread will terminate in next loop.
  401.                     LOGGER.log(Level.FINE, "Exception while " + Socks5Proxy.this + " was handling connection", e);
  402.                     CloseableUtil.maybeClose(socket, LOGGER);
  403.                 }
  404.             }
  405.         }

  406.         /**
  407.          * Negotiates a SOCKS5 connection and stores it on success.
  408.          *
  409.          * @param socket connection to the client
  410.          * @throws SmackException if client requests a connection in an unsupported way
  411.          * @throws IOException if a network error occurred
  412.          */
  413.         private void establishConnection(Socket socket) throws SmackException, IOException {
  414.             DataOutputStream out = new DataOutputStream(socket.getOutputStream());
  415.             DataInputStream in = new DataInputStream(socket.getInputStream());

  416.             // first byte is version should be 5
  417.             int b = in.read();
  418.             if (b != 5) {
  419.                 throw new SmackException.SmackMessageException("Only SOCKS5 supported: Peer send " + b + " but we expect 5");
  420.             }

  421.             // second byte number of authentication methods supported
  422.             b = in.read();

  423.             // read list of supported authentication methods
  424.             byte[] auth = new byte[b];
  425.             in.readFully(auth);

  426.             byte[] authMethodSelectionResponse = new byte[2];
  427.             authMethodSelectionResponse[0] = (byte) 0x05; // protocol version

  428.             // only authentication method 0, no authentication, supported
  429.             boolean noAuthMethodFound = false;
  430.             for (int i = 0; i < auth.length; i++) {
  431.                 if (auth[i] == (byte) 0x00) {
  432.                     noAuthMethodFound = true;
  433.                     break;
  434.                 }
  435.             }

  436.             if (!noAuthMethodFound) {
  437.                 authMethodSelectionResponse[1] = (byte) 0xFF; // no acceptable methods
  438.                 out.write(authMethodSelectionResponse);
  439.                 out.flush();
  440.                 throw new SmackException.SmackMessageException("Authentication method not supported");
  441.             }

  442.             authMethodSelectionResponse[1] = (byte) 0x00; // no-authentication method
  443.             out.write(authMethodSelectionResponse);
  444.             out.flush();

  445.             // receive connection request
  446.             byte[] connectionRequest = Socks5Utils.receiveSocks5Message(in);

  447.             // extract digest
  448.             String responseDigest = new String(connectionRequest, 5, connectionRequest[4], StandardCharsets.UTF_8);

  449.             // return error if digest is not allowed
  450.             if (!allowAllConnections && !Socks5Proxy.this.allowedConnections.contains(responseDigest)) {
  451.                 connectionRequest[1] = (byte) 0x05; // set return status to 5 (connection refused)
  452.                 out.write(connectionRequest);
  453.                 out.flush();

  454.                 throw new SmackException.SmackMessageException(
  455.                                 "Connection with digest '" + responseDigest + "' is not allowed");
  456.             }

  457.             // Store the connection before we send the return status.
  458.             Socks5Proxy.this.connectionMap.put(responseDigest, socket);

  459.             connectionRequest[1] = (byte) 0x00; // set return status to 0 (success)
  460.             out.write(connectionRequest);
  461.             out.flush();
  462.         }

  463.     }

  464.     public static Socket getSocketForDigest(String digest) {
  465.         for (Socks5Proxy socks5Proxy : RUNNING_PROXIES) {
  466.             Socket socket = socks5Proxy.getSocket(digest);
  467.             if (socket != null) {
  468.                 return socket;
  469.             }
  470.         }
  471.         return null;
  472.     }

  473.     static List<Socks5Proxy> getRunningProxies() {
  474.         return RUNNING_PROXIES;
  475.     }
  476. }