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) => {
match PebblesProxy::client(stream, authentication.clone().unwrap()) {
Ok(_) => (),
Err(e) => println!("Proxy failed to handle client: {}", e)
}
}
Err(e) => {
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<()> {
let mut buffer: [u8; 2] = [0; 2];
local_stream.read(&mut buffer[..])?;
let _version = buffer[0]; let number_of_methods = buffer[1];
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]);
}
if !methods.contains(&0x00) {
local_stream.write(&[SOCKS_VERSION, 0xFF])?;
return Err(std::io::Error::new(std::io::ErrorKind::Other, "Method not supported"));
}
local_stream.write(&[SOCKS_VERSION, 0x00])?;
let mut remote_stream: TcpStream = PebblesProxy::remote(authentication)?;
let mut incoming_local = local_stream.try_clone()?;
let mut incoming_remote = remote_stream.try_clone()?;
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(())
});
_ = handle_outgoing.join();
_ = handle_incoming.join();
Ok(())
}
fn remote(authentication:Authentication) -> IoResult<TcpStream> {
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"
)
);
}
};
remote_stream.write(&[
SOCKS_VERSION, 0x01, 0x02, ])?;
let mut buffer: [u8; 2] = [0; 2];
remote_stream.read(&mut buffer)?;
if buffer[0] != SOCKS_VERSION {
return Err(
Error::new(
std::io::ErrorKind::Other,
format!(
"Server does not support socks version: {}",
SOCKS_VERSION
)
)
);
}
if buffer[1] != 0x02 {
return Err(
Error::new(
std::io::ErrorKind::Other,
"Server does not support username/password authentication"
)
);
}
let mut auth_request = vec![
AUTHENTICATION_VERSION, ];
auth_request.push(authentication.username.len() as u8); auth_request.extend_from_slice(authentication.username.as_bytes());
auth_request.push(authentication.password.len() as u8); auth_request.extend_from_slice(authentication.password.as_bytes());
remote_stream.write(&auth_request)?;
let mut buffer: [u8; 2] = [0; 2];
remote_stream.read(&mut buffer)?;
if buffer[0] != AUTHENTICATION_VERSION {
return Err(
Error::new(
std::io::ErrorKind::Other,
format!(
"Unsupported username/password authentication version: {}",
buffer[0]
)
)
);
}
if buffer[1] != 0x00 {
return Err(
Error::new(
std::io::ErrorKind::Other,
"Server did not accept username/password"
)
);
}
Ok(remote_stream)
}
}