All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy Maven / Gradle / Ivy

Go to download

Smack is an Open Source XMPP (Jabber) client library for instant messaging and presence. This library provides the client side functionality as specified in the core XMPP specifications as related to the client side of said specifications.

The newest version!
/**
 * All rights reserved. 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;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPException;

/**
 * The Socks5Proxy class represents a local SOCKS5 proxy server. It can be enabled/disabled by
 * setting the localSocks5ProxyEnabled flag in the smack-config.xml or by
 * invoking {@link SmackConfiguration#setLocalSocks5ProxyEnabled(boolean)}. The proxy is enabled by
 * default.
 * 

* The port of the local SOCKS5 proxy can be configured by setting localSocks5ProxyPort * in the smack-config.xml or by invoking * {@link SmackConfiguration#setLocalSocks5ProxyPort(int)}. Default port is 7777. If you set the * port to a negative value Smack tries to the absolute value and all following until it finds an * open port. *

* If your application is running on a machine with multiple network interfaces or if you want to * provide your public address in case you are behind a NAT router, invoke * {@link #addLocalAddress(String)} or {@link #replaceLocalAddresses(List)} to modify the list of * local network addresses used for outgoing SOCKS5 Bytestream requests. *

* The local SOCKS5 proxy server refuses all connections except the ones that are explicitly allowed * in the process of establishing a SOCKS5 Bytestream ( * {@link Socks5BytestreamManager#establishSession(String)}). *

* This Implementation has the following limitations: *

    *
  • only supports the no-authentication authentication method
  • *
  • only supports the connect command and will not answer correctly to other * commands
  • *
  • only supports requests with the domain address type and will not correctly answer to requests * with other address types
  • *
* (see RFC 1928) * * @author Henning Staib */ public class Socks5Proxy { /* SOCKS5 proxy singleton */ private static Socks5Proxy socks5Server; /* reusable implementation of a SOCKS5 proxy server process */ private Socks5ServerProcess serverProcess; /* thread running the SOCKS5 server process */ private Thread serverThread; /* server socket to accept SOCKS5 connections */ private ServerSocket serverSocket; /* assigns a connection to a digest */ private final Map connectionMap = new ConcurrentHashMap(); /* list of digests connections should be stored */ private final List allowedConnections = Collections.synchronizedList(new LinkedList()); private final Set localAddresses = Collections.synchronizedSet(new LinkedHashSet()); /** * Private constructor. */ private Socks5Proxy() { this.serverProcess = new Socks5ServerProcess(); // add default local address try { this.localAddresses.add(InetAddress.getLocalHost().getHostAddress()); } catch (UnknownHostException e) { // do nothing } } /** * Returns the local SOCKS5 proxy server. * * @return the local SOCKS5 proxy server */ public static synchronized Socks5Proxy getSocks5Proxy() { if (socks5Server == null) { socks5Server = new Socks5Proxy(); } if (SmackConfiguration.isLocalSocks5ProxyEnabled()) { socks5Server.start(); } return socks5Server; } /** * Starts the local SOCKS5 proxy server. If it is already running, this method does nothing. */ public synchronized void start() { if (isRunning()) { return; } try { if (SmackConfiguration.getLocalSocks5ProxyPort() < 0) { int port = Math.abs(SmackConfiguration.getLocalSocks5ProxyPort()); for (int i = 0; i < 65535 - port; i++) { try { this.serverSocket = new ServerSocket(port + i); break; } catch (IOException e) { // port is used, try next one } } } else { this.serverSocket = new ServerSocket(SmackConfiguration.getLocalSocks5ProxyPort()); } if (this.serverSocket != null) { this.serverThread = new Thread(this.serverProcess); this.serverThread.start(); } } catch (IOException e) { // couldn't setup server System.err.println("couldn't setup local SOCKS5 proxy on port " + SmackConfiguration.getLocalSocks5ProxyPort() + ": " + e.getMessage()); } } /** * Stops the local SOCKS5 proxy server. If it is not running this method does nothing. */ public synchronized void stop() { if (!isRunning()) { return; } try { this.serverSocket.close(); } catch (IOException e) { // do nothing } if (this.serverThread != null && this.serverThread.isAlive()) { try { this.serverThread.interrupt(); this.serverThread.join(); } catch (InterruptedException e) { // do nothing } } this.serverThread = null; this.serverSocket = null; } /** * Adds the given address to the list of local network addresses. *

* Use this method if you want to provide multiple addresses in a SOCKS5 Bytestream request. * This may be necessary if your application is running on a machine with multiple network * interfaces or if you want to provide your public address in case you are behind a NAT router. *

* The order of the addresses used is determined by the order you add addresses. *

* Note that the list of addresses initially contains the address returned by * InetAddress.getLocalHost().getHostAddress(). You can replace the list of * addresses by invoking {@link #replaceLocalAddresses(List)}. * * @param address the local network address to add */ public void addLocalAddress(String address) { if (address == null) { throw new IllegalArgumentException("address may not be null"); } this.localAddresses.add(address); } /** * Removes the given address from the list of local network addresses. This address will then no * longer be used of outgoing SOCKS5 Bytestream requests. * * @param address the local network address to remove */ public void removeLocalAddress(String address) { this.localAddresses.remove(address); } /** * Returns an unmodifiable list of the local network addresses that will be used for streamhost * candidates of outgoing SOCKS5 Bytestream requests. * * @return unmodifiable list of the local network addresses */ public List getLocalAddresses() { return Collections.unmodifiableList(new ArrayList(this.localAddresses)); } /** * Replaces the list of local network addresses. *

* Use this method if you want to provide multiple addresses in a SOCKS5 Bytestream request and * want to define their order. This may be necessary if your application is running on a machine * with multiple network interfaces or if you want to provide your public address in case you * are behind a NAT router. * * @param addresses the new list of local network addresses */ public void replaceLocalAddresses(List addresses) { if (addresses == null) { throw new IllegalArgumentException("list must not be null"); } this.localAddresses.clear(); this.localAddresses.addAll(addresses); } /** * Returns the port of the local SOCKS5 proxy server. If it is not running -1 will be returned. * * @return the port of the local SOCKS5 proxy server or -1 if proxy is not running */ public int getPort() { if (!isRunning()) { return -1; } return this.serverSocket.getLocalPort(); } /** * Returns the socket for the given digest. A socket will be returned if the given digest has * been in the list of allowed transfers (see {@link #addTransfer(String)}) while the peer * connected to the SOCKS5 proxy. * * @param digest identifying the connection * @return socket or null if there is no socket for the given digest */ protected Socket getSocket(String digest) { return this.connectionMap.get(digest); } /** * Add the given digest to the list of allowed transfers. Only connections for allowed transfers * are stored and can be retrieved by invoking {@link #getSocket(String)}. All connections to * the local SOCKS5 proxy that don't contain an allowed digest are discarded. * * @param digest to be added to the list of allowed transfers */ protected void addTransfer(String digest) { this.allowedConnections.add(digest); } /** * Removes the given digest from the list of allowed transfers. After invoking this method * already stored connections with the given digest will be removed. *

* The digest should be removed after establishing the SOCKS5 Bytestream is finished, an error * occurred while establishing the connection or if the connection is not allowed anymore. * * @param digest to be removed from the list of allowed transfers */ protected void removeTransfer(String digest) { this.allowedConnections.remove(digest); this.connectionMap.remove(digest); } /** * Returns true if the local SOCKS5 proxy server is running, otherwise * false. * * @return true if the local SOCKS5 proxy server is running, otherwise * false */ public boolean isRunning() { return this.serverSocket != null; } /** * Implementation of a simplified SOCKS5 proxy server. */ private class Socks5ServerProcess implements Runnable { public void run() { while (true) { Socket socket = null; try { if (Socks5Proxy.this.serverSocket.isClosed() || Thread.currentThread().isInterrupted()) { return; } // accept connection socket = Socks5Proxy.this.serverSocket.accept(); // initialize connection establishConnection(socket); } catch (SocketException e) { /* * do nothing, if caused by closing the server socket, thread will terminate in * next loop */ } catch (Exception e) { try { if (socket != null) { socket.close(); } } catch (IOException e1) { /* do nothing */ } } } } /** * Negotiates a SOCKS5 connection and stores it on success. * * @param socket connection to the client * @throws XMPPException if client requests a connection in an unsupported way * @throws IOException if a network error occurred */ private void establishConnection(Socket socket) throws XMPPException, IOException { DataOutputStream out = new DataOutputStream(socket.getOutputStream()); DataInputStream in = new DataInputStream(socket.getInputStream()); // first byte is version should be 5 int b = in.read(); if (b != 5) { throw new XMPPException("Only SOCKS5 supported"); } // second byte number of authentication methods supported b = in.read(); // read list of supported authentication methods byte[] auth = new byte[b]; in.readFully(auth); byte[] authMethodSelectionResponse = new byte[2]; authMethodSelectionResponse[0] = (byte) 0x05; // protocol version // only authentication method 0, no authentication, supported boolean noAuthMethodFound = false; for (int i = 0; i < auth.length; i++) { if (auth[i] == (byte) 0x00) { noAuthMethodFound = true; break; } } if (!noAuthMethodFound) { authMethodSelectionResponse[1] = (byte) 0xFF; // no acceptable methods out.write(authMethodSelectionResponse); out.flush(); throw new XMPPException("Authentication method not supported"); } authMethodSelectionResponse[1] = (byte) 0x00; // no-authentication method out.write(authMethodSelectionResponse); out.flush(); // receive connection request byte[] connectionRequest = Socks5Utils.receiveSocks5Message(in); // extract digest String responseDigest = new String(connectionRequest, 5, connectionRequest[4]); // return error if digest is not allowed if (!Socks5Proxy.this.allowedConnections.contains(responseDigest)) { connectionRequest[1] = (byte) 0x05; // set return status to 5 (connection refused) out.write(connectionRequest); out.flush(); throw new XMPPException("Connection is not allowed"); } connectionRequest[1] = (byte) 0x00; // set return status to 0 (success) out.write(connectionRequest); out.flush(); // store connection Socks5Proxy.this.connectionMap.put(responseDigest, socket); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy