192 lines
5.3 KiB
Rust
192 lines
5.3 KiB
Rust
// Copyright 2022 The Grin Developers
|
|
//
|
|
// 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.
|
|
|
|
use crate::Error;
|
|
use grin_wallet_config::types::TorProxyConfig;
|
|
use std::collections::HashMap;
|
|
use std::convert::TryFrom;
|
|
use std::str;
|
|
use url::Host;
|
|
|
|
/// Tor Proxy
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct TorProxy {
|
|
/// proxy type used for the proxy, eg "socks4", "socks5", "http", "https"
|
|
pub transport: Option<String>,
|
|
/// Proxy address for the proxy, eg IP:PORT or Hostname
|
|
pub address: Option<String>,
|
|
/// Username for the proxy authentification
|
|
pub username: Option<String>,
|
|
/// Password for the proxy authentification
|
|
pub password: Option<String>,
|
|
/// computer goes through a firewall that only allows connections to certain ports
|
|
pub allowed_port: Option<Vec<u16>>,
|
|
}
|
|
|
|
impl Default for TorProxy {
|
|
fn default() -> TorProxy {
|
|
TorProxy {
|
|
transport: None,
|
|
address: None,
|
|
username: None,
|
|
password: None,
|
|
allowed_port: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TorProxy {
|
|
fn parse_host_port(addr: &str) -> Result<(String, Option<String>), Error> {
|
|
let host: String;
|
|
let str_port: Option<String>;
|
|
let address = addr
|
|
.chars()
|
|
.filter(|c| !c.is_whitespace())
|
|
.collect::<String>();
|
|
if address.starts_with('[') {
|
|
let split = address.split_once("]:").unwrap();
|
|
host = split.0.to_string();
|
|
str_port = Some(split.1.to_string());
|
|
} else if address.contains(":") && !address.ends_with(":") {
|
|
let split = address.split_once(":").unwrap();
|
|
host = split.0.to_string();
|
|
str_port = Some(split.1.to_string());
|
|
} else {
|
|
host = address.to_string();
|
|
str_port = None;
|
|
};
|
|
Ok((host, str_port))
|
|
}
|
|
|
|
pub fn parse_address(addr: &str) -> Result<(String, Option<u16>), Error> {
|
|
let (host, str_port) = TorProxy::parse_host_port(&addr)?;
|
|
let host = Host::parse(&host)
|
|
.map_err(|_e| Error::TorProxy(format!("Invalid host address: {}", host)))?;
|
|
let port = if let Some(p) = str_port {
|
|
let res = p
|
|
.parse::<u16>()
|
|
.map_err(|_e| Error::TorProxy(format!("Invalid port number: {}", p)))?;
|
|
Some(res)
|
|
} else {
|
|
None
|
|
};
|
|
Ok((host.to_string(), port))
|
|
}
|
|
|
|
pub fn to_hashmap(self) -> Result<HashMap<String, String>, Error> {
|
|
let mut hm = HashMap::new();
|
|
if let Some(ports) = self.allowed_port {
|
|
let mut allowed_ports = "".to_string();
|
|
let last_port = ports.last().unwrap().to_owned();
|
|
for port in ports.clone() {
|
|
allowed_ports.push_str(format!("*:{}", port).as_str());
|
|
if port != last_port {
|
|
allowed_ports.push_str(",");
|
|
}
|
|
}
|
|
hm.insert(
|
|
"ReachableAddresses".to_string(),
|
|
format!("{}", allowed_ports.clone()),
|
|
);
|
|
}
|
|
|
|
let transport = match self.transport {
|
|
Some(t) => t,
|
|
None => return Ok(hm),
|
|
};
|
|
match transport.as_str() {
|
|
"socks4" => {
|
|
hm.insert("Socks4Proxy".to_string(), self.address.unwrap());
|
|
Ok(hm)
|
|
}
|
|
"socks5" => {
|
|
hm.insert("Socks5Proxy".to_string(), self.address.unwrap());
|
|
|
|
if let Some(s) = self.username {
|
|
hm.insert("Socks5ProxyUsername".to_string(), s);
|
|
}
|
|
if let Some(s) = self.password {
|
|
hm.insert("Socks5ProxyPassword".to_string(), s);
|
|
}
|
|
Ok(hm)
|
|
}
|
|
"http" | "https" | "http(s)" => {
|
|
hm.insert("HTTPSProxy".to_string(), self.address.unwrap());
|
|
|
|
if let Some(user) = self.username {
|
|
let pass = self.password.unwrap_or("".to_string());
|
|
hm.insert(
|
|
"HTTPSProxyAuthenticator".to_string(),
|
|
format!("{}:{}", user, pass),
|
|
);
|
|
}
|
|
Ok(hm)
|
|
}
|
|
_ => Ok(hm),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<TorProxyConfig> for TorProxy {
|
|
type Error = Error;
|
|
|
|
fn try_from(tb: TorProxyConfig) -> Result<Self, Self::Error> {
|
|
if let Some(t) = tb.transport {
|
|
let transport = t.to_lowercase();
|
|
match transport.as_str() {
|
|
"socks4" | "socks5" | "http" | "https" | "http(s)" => {
|
|
// Can't parse socket address --> trying to parse a domain name
|
|
if let Some(address) = tb.address {
|
|
let address_addr: String;
|
|
let (host, port) = TorProxy::parse_address(&address)?;
|
|
if let Some(p) = port {
|
|
address_addr = format!("{}:{}", host, p);
|
|
} else {
|
|
address_addr = host
|
|
}
|
|
Ok(TorProxy {
|
|
transport: Some(transport.into()),
|
|
address: Some(address_addr),
|
|
username: tb.username,
|
|
password: tb.password,
|
|
allowed_port: tb.allowed_port,
|
|
})
|
|
} else {
|
|
let msg = format!(
|
|
"Missing proxy address: {} - must be <IP:PORT> or <Hostname>",
|
|
transport
|
|
);
|
|
return Err(Error::TorProxy(msg).into());
|
|
}
|
|
}
|
|
// Missing transport type
|
|
_ => {
|
|
let msg = format!(
|
|
"Invalid proxy transport: {} - must be socks4/socks5/http(s)",
|
|
transport
|
|
);
|
|
Err(Error::TorProxy(msg).into())
|
|
}
|
|
}
|
|
} else {
|
|
// In case the user want to allow only some ports
|
|
let ports = tb.allowed_port.unwrap();
|
|
Ok(TorProxy {
|
|
allowed_port: Some(ports),
|
|
..TorProxy::default()
|
|
})
|
|
}
|
|
}
|
|
}
|