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

org.eclipse.dirigible.components.api.mail.ConnectivitySocks5ProxySocket Maven / Gradle / Ivy

There is a newer version: 10.6.37
Show newest version
/*
 * Copyright (c) 2024 Eclipse Dirigible contributors
 *
 * All rights reserved. This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 *
 * SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0
 */
package org.eclipse.dirigible.components.api.mail;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;

/**
 * The Class ConnectivitySocks5ProxySocket.
 */
public class ConnectivitySocks5ProxySocket extends Socket {

    /** The Constant SOCKS5_VERSION. */
    private static final byte SOCKS5_VERSION = 0x05;

    /** The Constant SOCKS5_PASSWORD_AUTHENTICATION_METHOD. */
    private static final byte SOCKS5_PASSWORD_AUTHENTICATION_METHOD = (byte) 0x02;

    /** The Constant SOCKS5_PASSWORD_AUTHENTICATION_METHOD_VERSION. */
    private static final byte SOCKS5_PASSWORD_AUTHENTICATION_METHOD_VERSION = 0x01;

    /** The Constant SOCKS5_COMMAND_CONNECT_BYTE. */
    private static final byte SOCKS5_COMMAND_CONNECT_BYTE = 0x01;

    /** The Constant SOCKS5_COMMAND_REQUEST_RESERVED_BYTE. */
    private static final byte SOCKS5_COMMAND_REQUEST_RESERVED_BYTE = 0x00;

    /** The Constant SOCKS5_COMMAND_ADDRESS_TYPE_IPv4_BYTE. */
    private static final byte SOCKS5_COMMAND_ADDRESS_TYPE_IPv4_BYTE = 0x01;

    /** The Constant SOCKS5_COMMAND_ADDRESS_TYPE_DOMAIN_BYTE. */
    private static final byte SOCKS5_COMMAND_ADDRESS_TYPE_DOMAIN_BYTE = 0x03;

    /** The Constant SOCKS5_AUTHENTICATION_METHODS_COUNT. */
    private static final byte SOCKS5_AUTHENTICATION_METHODS_COUNT = 0x01;

    /** The Constant SOCKS5_PASSWORD_AUTHENTICATION_METHOD_UNSIGNED_VALUE. */
    private static final int SOCKS5_PASSWORD_AUTHENTICATION_METHOD_UNSIGNED_VALUE = 0x02 & 0xFF;

    /** The Constant SOCKS5_AUTHENTICATION_SUCCESS_BYTE. */
    private static final byte SOCKS5_AUTHENTICATION_SUCCESS_BYTE = 0x00;

    /** The proxy address. */
    private final InetSocketAddress proxyAddress;

    /** The username. */
    private final String username;

    /** The password. */
    private final String password;

    /**
     * Instantiates a new connectivity socks 5 proxy socket.
     *
     * @param host the host
     * @param port the port
     * @param username the username
     * @param password the password
     */
    public ConnectivitySocks5ProxySocket(String host, String port, String username, String password) {
        this.proxyAddress = new InetSocketAddress(host, Integer.parseInt(port));
        this.username = username;
        this.password = password;
    }

    /**
     * Connect.
     *
     * @param endpoint the endpoint
     * @param timeout the timeout
     * @throws IOException Signals that an I/O exception has occurred.
     */
    @Override
    public void connect(SocketAddress endpoint, int timeout) throws IOException {
        super.connect(this.proxyAddress, timeout);

        OutputStream outputStream = getOutputStream();

        executeSOCKS5InitialRequest(outputStream);

        executeSOCKS5AuthenticationRequest(outputStream);

        executeSOCKS5ConnectRequest(outputStream, (InetSocketAddress) endpoint);
    }

    /**
     * Connect.
     *
     * @param endpoint the endpoint
     * @throws IOException Signals that an I/O exception has occurred.
     */
    @Override
    public void connect(SocketAddress endpoint) throws IOException {
        this.connect(endpoint, 0);
    }

    /**
     * Execute SOCKS 5 initial request.
     *
     * @param outputStream the output stream
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void executeSOCKS5InitialRequest(OutputStream outputStream) throws IOException {
        byte[] initialRequest = createInitialSOCKS5Request();
        outputStream.write(initialRequest);

        assertServerInitialResponse();
    }

    /**
     * Creates the initial SOCKS 5 request.
     *
     * @return the byte[]
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private byte[] createInitialSOCKS5Request() throws IOException {
        try (ByteArrayOutputStream byteArraysStream = new ByteArrayOutputStream()) {
            byteArraysStream.write(SOCKS5_VERSION);
            byteArraysStream.write(SOCKS5_AUTHENTICATION_METHODS_COUNT);
            byteArraysStream.write(SOCKS5_PASSWORD_AUTHENTICATION_METHOD);
            return byteArraysStream.toByteArray();
        }
    }

    /**
     * Assert server initial response.
     *
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void assertServerInitialResponse() throws IOException {
        try (InputStream inputStream = getInputStream()) {
            int versionByte = inputStream.read();
            if (SOCKS5_VERSION != versionByte) {
                throw new SocketException("Unsupported SOCKS version - expected " + SOCKS5_VERSION + ", but received " + versionByte);
            }

            int authenticationMethodValue = inputStream.read();
            if (SOCKS5_PASSWORD_AUTHENTICATION_METHOD_UNSIGNED_VALUE != authenticationMethodValue) {
                throw new SocketException("Unsupported authentication method value - expected "
                        + SOCKS5_PASSWORD_AUTHENTICATION_METHOD_UNSIGNED_VALUE + ", but received " + authenticationMethodValue);
            }
        }
    }

    /**
     * Execute SOCKS 5 authentication request.
     *
     * @param outputStream the output stream
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void executeSOCKS5AuthenticationRequest(OutputStream outputStream) throws IOException {
        byte[] authenticationRequest = createPasswordAuthenticationRequest();
        outputStream.write(authenticationRequest);

        assertAuthenticationResponse();
    }

    /**
     * Creates the password authentication request.
     *
     * @return the byte[]
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private byte[] createPasswordAuthenticationRequest() throws IOException {
        try (ByteArrayOutputStream byteArraysStream = new ByteArrayOutputStream()) {
            byteArraysStream.write(SOCKS5_PASSWORD_AUTHENTICATION_METHOD_VERSION);
            byteArraysStream.write(ByteBuffer.allocate(1)
                                             .put((byte) this.username.getBytes().length)
                                             .array());
            byteArraysStream.write(this.username.getBytes());
            byteArraysStream.write(ByteBuffer.allocate(1)
                                             .put((byte) this.password.getBytes().length)
                                             .array());
            byteArraysStream.write(this.password.getBytes());
            return byteArraysStream.toByteArray();
        }
    }

    /**
     * Assert authentication response.
     *
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void assertAuthenticationResponse() throws IOException {
        try (InputStream inputStream = getInputStream()) {
            int authenticationMethodVersion = inputStream.read();
            if (SOCKS5_PASSWORD_AUTHENTICATION_METHOD_VERSION != authenticationMethodVersion) {
                throw new SocketException("Unsupported authentication method version - expected "
                        + SOCKS5_PASSWORD_AUTHENTICATION_METHOD_VERSION + ", but received " + authenticationMethodVersion);
            }

            int authenticationStatus = inputStream.read();
            if (SOCKS5_AUTHENTICATION_SUCCESS_BYTE != authenticationStatus) {
                throw new SocketException("Authentication failed!");
            }
        }
    }

    /**
     * Execute SOCKS 5 connect request.
     *
     * @param outputStream the output stream
     * @param endpoint the endpoint
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void executeSOCKS5ConnectRequest(OutputStream outputStream, InetSocketAddress endpoint) throws IOException {
        byte[] commandRequest = createConnectCommandRequest(endpoint);
        outputStream.write(commandRequest);

        assertConnectCommandResponse();
    }

    /**
     * Creates the connect command request.
     *
     * @param endpoint the endpoint
     * @return the byte[]
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private byte[] createConnectCommandRequest(InetSocketAddress endpoint) throws IOException {
        String host = endpoint.getHostName();
        int port = endpoint.getPort();
        try (ByteArrayOutputStream byteArraysStream = new ByteArrayOutputStream()) {
            byteArraysStream.write(SOCKS5_VERSION);
            byteArraysStream.write(SOCKS5_COMMAND_CONNECT_BYTE);
            byteArraysStream.write(SOCKS5_COMMAND_REQUEST_RESERVED_BYTE);
            byte[] hostToIPv4 = parseHostToIPv4(host);
            if (hostToIPv4 != null) {
                byteArraysStream.write(SOCKS5_COMMAND_ADDRESS_TYPE_IPv4_BYTE);
                byteArraysStream.write(hostToIPv4);
            } else {
                byteArraysStream.write(SOCKS5_COMMAND_ADDRESS_TYPE_DOMAIN_BYTE);
                byteArraysStream.write(ByteBuffer.allocate(1)
                                                 .put((byte) host.getBytes().length)
                                                 .array());
                byteArraysStream.write(host.getBytes());
            }
            byteArraysStream.write(ByteBuffer.allocate(2)
                                             .putShort((short) port)
                                             .array());
            return byteArraysStream.toByteArray();
        }
    }

    /**
     * Assert connect command response.
     *
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void assertConnectCommandResponse() throws IOException {
        try (InputStream inputStream = getInputStream()) {
            int versionByte = inputStream.read();
            if (SOCKS5_VERSION != versionByte) {
                throw new SocketException("Unsupported SOCKS version - expected " + SOCKS5_VERSION + ", but received " + versionByte);
            }

            int connectStatusByte = inputStream.read();
            assertConnectStatus(connectStatusByte);

            readRemainingCommandResponseBytes(inputStream);
        }
    }

    /**
     * Assert connect status.
     *
     * @param commandConnectStatus the command connect status
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void assertConnectStatus(int commandConnectStatus) throws IOException {
        if (commandConnectStatus == 0) {
            return;
        }

        String commandConnectStatusTranslation;
        switch (commandConnectStatus) {
            case 1:
                commandConnectStatusTranslation = "FAILURE";
                break;
            case 2:
                commandConnectStatusTranslation = "FORBIDDEN";
                break;
            case 3:
                commandConnectStatusTranslation = "NETWORK_UNREACHABLE";
                break;
            case 4:
                commandConnectStatusTranslation = "HOST_UNREACHABLE";
                break;
            case 5:
                commandConnectStatusTranslation = "CONNECTION_REFUSED";
                break;
            case 6:
                commandConnectStatusTranslation = "TTL_EXPIRED";
                break;
            case 7:
                commandConnectStatusTranslation = "COMMAND_UNSUPPORTED";
                break;
            case 8:
                commandConnectStatusTranslation = "ADDRESS_UNSUPPORTED";
                break;
            default:
                commandConnectStatusTranslation = "UNKNOWN";
                break;
        }
        throw new SocketException("SOCKS5 command failed with status: " + commandConnectStatusTranslation);
    }

    /**
     * Parses the host to I pv 4.
     *
     * @param hostName the host name
     * @return the byte[]
     */
    private byte[] parseHostToIPv4(String hostName) {
        byte[] parsedHostName = null;
        String[] virtualHostOctets = hostName.split("\\.", -1);
        int octetsCount = virtualHostOctets.length;
        if (octetsCount == 4) {
            try {
                byte[] ipOctets = new byte[octetsCount];
                for (int i = 0; i < octetsCount; i++) {
                    int currentOctet = Integer.parseInt(virtualHostOctets[i]);
                    if ((currentOctet < 0) || (currentOctet > 255)) {
                        throw new IllegalArgumentException("Provided octet %s is not in the range of [0-255]: " + currentOctet);
                    }
                    ipOctets[i] = (byte) currentOctet;
                }
                parsedHostName = ipOctets;
            } catch (IllegalArgumentException ex) {
                return null;
            }
        }

        return parsedHostName;
    }

    /**
     * Read remaining command response bytes.
     *
     * @param inputStream the input stream
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void readRemainingCommandResponseBytes(InputStream inputStream) throws IOException {
        inputStream.read(); // skipping over SOCKS5 reserved byte
        int addressTypeByte = inputStream.read();
        if (SOCKS5_COMMAND_ADDRESS_TYPE_IPv4_BYTE == addressTypeByte) {
            for (int i = 0; i < 6; i++) {
                inputStream.read();
            }
        } else if (SOCKS5_COMMAND_ADDRESS_TYPE_DOMAIN_BYTE == addressTypeByte) {
            int domainNameLength = inputStream.read();
            int portBytes = 2;
            inputStream.read(new byte[domainNameLength + portBytes], 0, domainNameLength + portBytes);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy