// 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, /// Proxy address for the proxy, eg IP:PORT or Hostname pub address: Option, /// Username for the proxy authentification pub username: Option, /// Password for the proxy authentification pub password: Option, /// computer goes through a firewall that only allows connections to certain ports pub allowed_port: Option>, } 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), Error> { let host: String; let str_port: Option; let address = addr .chars() .filter(|c| !c.is_whitespace()) .collect::(); 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), 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::() .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, 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 for TorProxy { type Error = Error; fn try_from(tb: TorProxyConfig) -> Result { 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 or ", 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() }) } } }