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

org.simplejavamail.internal.authenticatedsockssupport.socks5server.Socks5Handler Maven / Gradle / Ivy

/*
 * Copyright © 2009 Benny Bottema ([email protected])
 *
 * 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.simplejavamail.internal.authenticatedsockssupport.socks5server;

import org.simplejavamail.api.internal.authenticatedsockssupport.common.Socks5Bridge;
import org.simplejavamail.internal.authenticatedsockssupport.common.SocksException;
import org.simplejavamail.internal.authenticatedsockssupport.socks5server.io.SocketPipe;
import org.simplejavamail.internal.authenticatedsockssupport.socks5server.msg.CommandMessage;
import org.simplejavamail.internal.authenticatedsockssupport.socks5server.msg.CommandResponseMessage;
import org.simplejavamail.internal.authenticatedsockssupport.socks5server.msg.MethodSelectionMessage;
import org.simplejavamail.internal.authenticatedsockssupport.socks5server.msg.ServerReply;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;

public class Socks5Handler implements Runnable {

	private static final Logger LOGGER = LoggerFactory.getLogger(Socks5Handler.class);
	private static final Logger SOCKS5BRIDGE_LOGGER = LoggerFactory.getLogger("socks5bridge");
	private static final byte[] METHOD_SELECTION_RESPONSE = { (byte) 0x5, (byte) 0x00 };
	private static final int CONNECT_COMMAND = 0x01;

	public static final int VERSION = 0x5;

	private final SocksSession session;
	private final Socks5Bridge socks5Bridge;

	public Socks5Handler(final SocksSession session, final Socks5Bridge socks5Bridge) {
		this.session = session;
		this.socks5Bridge = socks5Bridge;
	}

	@Override
	public void run() {
		try {
			handle(session, socks5Bridge);
		} catch (final Exception e) {
			LOGGER.error(e.getMessage(), e);
		} finally {
			session.close();
		}
	}

	private void handle(final SocksSession session, final Socks5Bridge socks5Bridge)
			throws IOException {
		if (MethodSelectionMessage.readVersion(session.getInputStream()) != VERSION) {
			throw new SocksException("Protocol error");
		}

		LOGGER.debug("SESSION[{}]", session.getId());
		// send select method.
		session.write(METHOD_SELECTION_RESPONSE);

		final CommandMessage commandMessage = new CommandMessage();
		commandMessage.read(session.getInputStream());

		// If there is a SOCKS exception in command message, It will send a right response to client.
		if (commandMessage.hasSocksException()) {
			final ServerReply serverReply = commandMessage.getSocksServerReplyException().getServerReply();
			session.write(CommandResponseMessage.getBytes(serverReply));
			LOGGER.debug("SESSION[{}] will close, because {}", session.getId(), serverReply);
			return;
		}

		if (commandMessage.getCommand() != CONNECT_COMMAND) {
			throw new SocksException("Only CONNECT command is supported");
		}
		doConnect(session, commandMessage, socks5Bridge);
	}

	private void doConnect(final SocksSession session, final CommandMessage commandMessage, final Socks5Bridge socks5Bridge)
			throws IOException {
		ServerReply reply;
		Socket socket = null;
		int bindPort = 0;
		final InetAddress targetServerAddress = commandMessage.getInetAddress();
		final int targetServerPort = commandMessage.getPort();

		// set default bind address.
		InetAddress bindAddress = new InetSocketAddress(0).getAddress();
		try {
			// the magic happens here...
			socket = socks5Bridge.connect(String.valueOf(this.session.getId()), targetServerAddress, targetServerPort);
			bindAddress = socket.getLocalAddress();
			bindPort = socket.getLocalPort();
			reply = ServerReply.SUCCEEDED;
		} catch (final IOException e) {
			//noinspection IfCanBeSwitch
			if (e.getMessage().equals("Connection refused")) {
				reply = ServerReply.CONNECTION_REFUSED;
			} else if (e.getMessage().equals("Operation timed out")) {
				reply = ServerReply.TTL_EXPIRED;
			} else if (e.getMessage().equals("Network is unreachable")) {
				reply = ServerReply.NETWORK_UNREACHABLE;
			} else if (e.getMessage().equals("Connection timed out")) {
				reply = ServerReply.TTL_EXPIRED;
			} else {
				reply = ServerReply.GENERAL_SOCKS_SERVER_FAILURE;
			}
			final InetSocketAddress remoteAddress = new InetSocketAddress(targetServerAddress, targetServerPort);

			if (e.getMessage().equals("Permission denied: connect")) {
				final String msg = "Permission denied - unable to establish outbound connection to proxy. Perhaps blocked by a firewall?";
				LOGGER.info("connect {} [{}] exception: {}", session.getId(), remoteAddress, msg);
				SOCKS5BRIDGE_LOGGER.error("connecting to {}: {}", remoteAddress, msg);
			} else {
				LOGGER.info("SESSION[{}] connect {} [{}] exception: {}", session.getId(), remoteAddress, reply, e.getMessage());
			}
		}

		session.write(CommandResponseMessage.getBytes(reply, bindAddress, bindPort));

		if (reply != ServerReply.SUCCEEDED) {
			session.close();
			return;
		}

		final SocketPipe pipe = new SocketPipe(session.getSocket(), socket);
		pipe.setName("SESSION[" + session.getId() + "]");
		pipe.start(); // This method will build tow thread to run tow internal pipes.

		// wait for pipe exit.
		while (pipe.isRunning()) {
			try {
				Thread.sleep(500);
			} catch (final InterruptedException e) {
				pipe.stop();
				session.close();
				LOGGER.info("SESSION[{}] closed from {}", session.getId(), session.getClientAddress());
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy