pebbles 0.0.103

The Worst Web Automation Framework Ever. (╯°□°)╯︵ ┻━┻
Documentation
use std::io::{Read, Write, copy, Result as IoResult, Error};
use std::net::{TcpListener, TcpStream};
use std::thread;
use crate::result::{ PebblesError, PebblesErrorDetails };
use crate::raise;

const SOCKS_VERSION: u8 = 0x05;
const AUTHENTICATION_VERSION: u8 = 0x01;

#[derive(Clone)]
pub struct Authentication {
    pub username: String,
    pub password: String,
}

#[derive(Clone)]
pub struct PebblesProxy {
    pub address: String,
    pub port: u16,
}

impl PebblesProxy {
    pub fn new(address: String, port: u16, authentication:Option<Authentication>) -> Result<Self, PebblesError> {
        if authentication.is_none() {
            return Ok(PebblesProxy {
                address,
                port,
            });
        } else {
            let server_socket = match TcpListener::bind("127.0.0.1:0") {
                Ok(value) => value,
                Err(e) => raise!(PebblesError::ProxyError, format!("Proxy could not bind to localhost: {:#?}", e))
            };
            let address = match server_socket.local_addr() {
                Ok(value) => value,
                Err(e) => raise!(PebblesError::ProxyError, format!("Proxy could not load local address: {:#?}", e))
            };
            thread::spawn(move || {
                for stream in server_socket.incoming() {
                    match stream {
                        Ok(stream) => {
                            // Handle new client connection
                            match PebblesProxy::client(stream, authentication.clone().unwrap()) {
                                Ok(_) => (),
                                Err(e) => println!("Proxy failed to handle client: {}", e)
                            }
                        }
                        Err(e) => { 
                            // Handle connection error
                            println!("Proxy failed to handle incoming connection: {}", e)
                        }
                    }
                }
            });
            return Ok(PebblesProxy {
                address: address.ip().to_string(),
                port: address.port() as u16,
            })
        }
    }

    fn client(mut local_stream:TcpStream, authentication:Authentication) -> IoResult<()> {
        // greeting header
        let mut buffer: [u8; 2] = [0; 2];
        local_stream.read(&mut buffer[..])?;
        let _version = buffer[0]; // should be the same as SOCKS_VERSION
        let number_of_methods = buffer[1];
    
        // authentication methods
        let mut methods: Vec<u8> = vec![];
        for _ in 0..number_of_methods {
            let mut next_method: [u8; 1] = [0; 1];
            local_stream.read(&mut next_method[..])?;
            methods.push(next_method[0]);
        }
    
        // only accept no authentication
        if !methods.contains(&0x00) {
            // no acceptable methods were offered
            local_stream.write(&[SOCKS_VERSION, 0xFF])?;
            return Err(std::io::Error::new(std::io::ErrorKind::Other, "Method not supported"));
        }
    
        // we choose no authentication
        local_stream.write(&[SOCKS_VERSION, 0x00])?;
    
        // create a TcpStream to the remote server
        let mut remote_stream: TcpStream = PebblesProxy::remote(authentication)?;
    
        // clone our streams
        let mut incoming_local = local_stream.try_clone()?;
        let mut incoming_remote = remote_stream.try_clone()?;
    
        // copy the data from one to the other
        let handle_outgoing = thread::spawn(move || -> std::io::Result<()> {
            copy(&mut local_stream, &mut remote_stream)?;
            Ok(())
        });
        let handle_incoming = thread::spawn(move || -> std::io::Result<()> {
            copy(&mut incoming_remote, &mut incoming_local)?;
            Ok(())
        });
    
        // if we get any errors now its not our problem
        _ = handle_outgoing.join();
        _ = handle_incoming.join();
    
        // The End.
        Ok(())
    }
    
    fn remote(authentication:Authentication) -> IoResult<TcpStream> {
        // create a connection
        let mut remote_stream: TcpStream = match TcpStream::connect(("188.74.183.10", 8279)) {
            Ok(remote) => remote,
            Err(_) => {
                return Err(
                    std::io::Error::new(
                        std::io::ErrorKind::Other, 
                        "Failed to connect to remote proxy"
                    )
                );
            }
        };
    
        // greeting header
        remote_stream.write(&[
            SOCKS_VERSION, // SOCKS version
            0x01, // Number of authentication methods
            0x02, // Username/password authentication
        ])?;
    
        // Receive the servers reply
        let mut buffer: [u8; 2]  = [0; 2];
        remote_stream.read(&mut buffer)?;
    
        // Check the SOCKS version
        if buffer[0] != SOCKS_VERSION {
            return Err(
                Error::new(
                    std::io::ErrorKind::Other, 
                    format!(
                        "Server does not support socks version: {}",
                        SOCKS_VERSION
                    )
                )
            );
        }
        
        // Check the authentication method
        if buffer[1] != 0x02 {
            return Err(
                Error::new(
                    std::io::ErrorKind::Other, 
                    "Server does not support username/password authentication"
                )
            );
        }
    
        // Create a username/password negotiation request    
        let mut auth_request = vec![
            AUTHENTICATION_VERSION, // Username/password authentication version
        ];
        auth_request.push(authentication.username.len() as u8); // Username length
        auth_request.extend_from_slice(authentication.username.as_bytes());
        auth_request.push(authentication.password.len() as u8); // Password length
        auth_request.extend_from_slice(authentication.password.as_bytes());
    
        // Send the username/password negotiation request
        remote_stream.write(&auth_request)?;
    
        // Receive the username/password negotiation reply/welcome message
        let mut buffer: [u8; 2] = [0; 2];
        remote_stream.read(&mut buffer)?;
    
        // Check the username/password authentication version
        if buffer[0] != AUTHENTICATION_VERSION {
            return Err(
                Error::new(
                    std::io::ErrorKind::Other, 
                    format!(
                        "Unsupported username/password authentication version: {}", 
                        buffer[0]
                    )
                )
            );
        }
    
        // Check the username/password authentication status
        if buffer[1] != 0x00 {
            return Err(
                Error::new(
                    std::io::ErrorKind::Other, 
                    "Server did not accept username/password"
                )
            );
        }
    
        // Return the stream
        Ok(remote_stream)
    }

}