mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-01-20 19:11:09 +03:00
TOR bridge + TOR Proxy + migration config_file_version (#617)
* tor bridge config and args * migration `config_file_version=2` * small fixes typo, comment etc.. * support: snowflake, meek_lite, obsf4 and tor proxy * remove useless serde * improve migrate function * few fixes * add bridge flags to pay and receive + few fixes * some improvements
This commit is contained in:
parent
f5dbed2014
commit
c424a0ed10
18 changed files with 1338 additions and 41 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1546,6 +1546,7 @@ dependencies = [
|
|||
name = "grin_wallet_impls"
|
||||
version = "5.1.0-alpha.1"
|
||||
dependencies = [
|
||||
"base64 0.12.3",
|
||||
"blake2-rfc",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
|
@ -1570,6 +1571,7 @@ dependencies = [
|
|||
"sysinfo",
|
||||
"timer",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid",
|
||||
"x25519-dalek 0.6.0",
|
||||
]
|
||||
|
|
|
@ -2451,6 +2451,8 @@ pub fn try_slatepack_sync_workflow(
|
|||
&tor_addr.to_http_str(),
|
||||
&tor_config.as_ref().unwrap().socks_proxy_addr,
|
||||
&tor_config.as_ref().unwrap().send_config_dir,
|
||||
tor_config.as_ref().unwrap().bridge.clone(),
|
||||
tor_config.as_ref().unwrap().proxy.clone(),
|
||||
) {
|
||||
Ok(s) => Some(s),
|
||||
Err(e) => {
|
||||
|
|
|
@ -19,6 +19,14 @@ use std::collections::HashMap;
|
|||
fn comments() -> HashMap<String, String> {
|
||||
let mut retval = HashMap::new();
|
||||
|
||||
retval.insert(
|
||||
"config_file_version".to_string(),
|
||||
"
|
||||
#Version of the Generated Configuration File for the Grin Wallet (DO NOT EDIT)
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"[wallet]".to_string(),
|
||||
"
|
||||
|
@ -124,6 +132,22 @@ fn comments() -> HashMap<String, String> {
|
|||
retval.insert(
|
||||
"[logging]".to_string(),
|
||||
"
|
||||
#Type of proxy, eg \"socks4\", \"socks5\", \"http\", \"https\"
|
||||
#transport = \"https\"
|
||||
|
||||
#Proxy address, eg IP:PORT or Hostname
|
||||
#server = \"\"
|
||||
|
||||
#Username for the proxy server authentification
|
||||
#user = \"\"
|
||||
|
||||
#Password for the proxy server authentification
|
||||
#pass = \"\"
|
||||
|
||||
#This computer goes through a firewall that only allows connections to certain ports (Optional)
|
||||
#allowed_port = [80, 443]
|
||||
|
||||
|
||||
#########################################
|
||||
### LOGGING CONFIGURATION ###
|
||||
#########################################
|
||||
|
@ -214,14 +238,6 @@ fn comments() -> HashMap<String, String> {
|
|||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"socks_proxy_addr".to_string(),
|
||||
"
|
||||
# TOR (SOCKS) proxy server address
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"send_config_dir".to_string(),
|
||||
"
|
||||
|
@ -230,6 +246,37 @@ fn comments() -> HashMap<String, String> {
|
|||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"[tor.bridge]".to_string(),
|
||||
"
|
||||
#########################################
|
||||
### TOR BRIDGE ###
|
||||
#########################################
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"[tor.proxy]".to_string(),
|
||||
"
|
||||
#Tor bridge relay: allow to send and receive via TOR in a country where it is censored.
|
||||
#Enable it by entering a single bridge line. To disable it, you must comment it.
|
||||
#Support of the transport: obfs4, meek and snowflake.
|
||||
#obfs4proxy or snowflake client binary must be installed and on your path.
|
||||
#For example, the bridge line must be in the following format for obfs4 transport: \"obfs4 [IP:PORT] [FINGERPRINT] cert=[CERT] iat-mode=[IAT-MODE]\"
|
||||
#bridge_line = \"\"
|
||||
|
||||
#Plugging client option, needed only for snowflake (let it empty if you want to use the default option of tor) or debugging purpose
|
||||
#client_option = \"\"
|
||||
|
||||
|
||||
#########################################
|
||||
### TOR PROXY ###
|
||||
#########################################
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval
|
||||
}
|
||||
|
||||
|
@ -261,3 +308,92 @@ pub fn insert_comments(orig: String) -> String {
|
|||
}
|
||||
ret_val
|
||||
}
|
||||
|
||||
pub fn migrate_comments(
|
||||
old_config: String,
|
||||
new_config: String,
|
||||
old_version: Option<u32>,
|
||||
) -> String {
|
||||
let comments = comments();
|
||||
// Prohibe the key we are basing on to introduce new comments for [tor.proxy]
|
||||
let prohibited_key = match old_version {
|
||||
None => vec!["[logging]"],
|
||||
Some(_) => vec![],
|
||||
};
|
||||
let mut vec_old_conf = vec![];
|
||||
let mut hm_key_cmt_old = HashMap::new();
|
||||
let old_conf: Vec<&str> = old_config.split_inclusive('\n').collect();
|
||||
// collect old key in a vec and insert old key/comments from the old conf in a hashmap
|
||||
let vec_key_old = old_conf
|
||||
.iter()
|
||||
.filter_map(|line| {
|
||||
let line_nospace = line.trim();
|
||||
let is_ascii_control = line_nospace.chars().all(|x| x.is_ascii_control());
|
||||
match line.contains("#") || is_ascii_control {
|
||||
true => {
|
||||
vec_old_conf.push(line.to_owned());
|
||||
None
|
||||
}
|
||||
false => {
|
||||
let comments: String =
|
||||
vec_old_conf.iter().map(|s| s.chars()).flatten().collect();
|
||||
let key = get_key(line_nospace);
|
||||
match !(key == "NOT_FOUND") {
|
||||
true => {
|
||||
vec_old_conf.clear();
|
||||
hm_key_cmt_old.insert(key.clone(), comments);
|
||||
Some(key)
|
||||
}
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let new_conf: Vec<&str> = new_config.split_inclusive('\n').collect();
|
||||
// collect new key and the whole key line from the new config
|
||||
let vec_key_cmt_new = new_conf
|
||||
.iter()
|
||||
.filter_map(|line| {
|
||||
let line_nospace = line.trim();
|
||||
let is_ascii_control = line_nospace.chars().all(|x| x.is_ascii_control());
|
||||
match !(line.contains("#") || is_ascii_control) {
|
||||
true => {
|
||||
let key = get_key(line_nospace);
|
||||
match !(key == "NOT_FOUND") {
|
||||
true => Some((key, line_nospace.to_string())),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
false => None,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<(String, String)>>();
|
||||
|
||||
let mut new_config_str = String::from("");
|
||||
// Merging old comments in the new config (except if the key is contained in the prohibited vec) with all new introduced key comments
|
||||
for (key, key_line) in vec_key_cmt_new {
|
||||
let old_key_exist = vec_key_old.iter().any(|old_key| *old_key == key);
|
||||
let key_fmt = format!("{}\n", key_line);
|
||||
if old_key_exist {
|
||||
if prohibited_key.contains(&key.as_str()) {
|
||||
// push new config key/comments
|
||||
let value = comments.get(&key).unwrap();
|
||||
new_config_str.push_str(value);
|
||||
new_config_str.push_str(&key_fmt);
|
||||
} else {
|
||||
// push old config key/comment
|
||||
let value = hm_key_cmt_old.get(&key).unwrap();
|
||||
new_config_str.push_str(value);
|
||||
new_config_str.push_str(&key_fmt);
|
||||
}
|
||||
} else {
|
||||
// old key does not exist, we push new key/comments
|
||||
let value = comments.get(&key).unwrap();
|
||||
new_config_str.push_str(value);
|
||||
new_config_str.push_str(&key_fmt);
|
||||
}
|
||||
}
|
||||
new_config_str
|
||||
}
|
||||
|
|
|
@ -21,13 +21,14 @@ use std::env;
|
|||
use std::fs::{self, File};
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use toml;
|
||||
|
||||
use crate::comments::insert_comments;
|
||||
use crate::comments::{insert_comments, migrate_comments};
|
||||
use crate::core::global;
|
||||
use crate::types::{ConfigError, GlobalWalletConfig, GlobalWalletConfigMembers};
|
||||
use crate::types::{
|
||||
ConfigError, GlobalWalletConfig, GlobalWalletConfigMembers, TorBridgeConfig, TorProxyConfig,
|
||||
};
|
||||
use crate::types::{TorConfig, WalletConfig};
|
||||
use crate::util::logger::LoggingConfig;
|
||||
|
||||
|
@ -187,6 +188,7 @@ pub fn initial_setup_wallet(
|
|||
impl Default for GlobalWalletConfigMembers {
|
||||
fn default() -> GlobalWalletConfigMembers {
|
||||
GlobalWalletConfigMembers {
|
||||
config_file_version: Some(2),
|
||||
logging: Some(LoggingConfig::default()),
|
||||
tor: Some(TorConfig::default()),
|
||||
wallet: WalletConfig::default(),
|
||||
|
@ -245,10 +247,13 @@ impl GlobalWalletConfig {
|
|||
|
||||
/// Read config
|
||||
fn read_config(mut self) -> Result<GlobalWalletConfig, ConfigError> {
|
||||
let mut file = File::open(self.config_file_path.as_mut().unwrap())?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
let fixed = GlobalWalletConfig::fix_warning_level(contents);
|
||||
let config_file_path = self.config_file_path.as_mut().unwrap();
|
||||
let contents = fs::read_to_string(config_file_path.clone())?;
|
||||
let migrated = GlobalWalletConfig::migrate_config_file_version_none_to_2(
|
||||
contents,
|
||||
config_file_path.to_owned(),
|
||||
)?;
|
||||
let fixed = GlobalWalletConfig::fix_warning_level(migrated);
|
||||
let decoded: Result<GlobalWalletConfigMembers, toml::de::Error> = toml::from_str(&fixed);
|
||||
match decoded {
|
||||
Ok(gc) => {
|
||||
|
@ -306,14 +311,60 @@ impl GlobalWalletConfig {
|
|||
}
|
||||
|
||||
/// Write configuration to a file
|
||||
pub fn write_to_file(&mut self, name: &str) -> Result<(), ConfigError> {
|
||||
pub fn write_to_file(
|
||||
&mut self,
|
||||
name: &str,
|
||||
migration: bool,
|
||||
old_config: Option<String>,
|
||||
old_version: Option<u32>,
|
||||
) -> Result<(), ConfigError> {
|
||||
let conf_out = self.ser_config()?;
|
||||
let fixed_config = GlobalWalletConfig::fix_log_level(conf_out);
|
||||
let commented_config = insert_comments(fixed_config);
|
||||
let commented_config = if migration {
|
||||
migrate_comments(old_config.unwrap(), conf_out, old_version)
|
||||
} else {
|
||||
let fixed_config = GlobalWalletConfig::fix_log_level(conf_out);
|
||||
insert_comments(fixed_config)
|
||||
};
|
||||
let mut file = File::create(name)?;
|
||||
file.write_all(commented_config.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
/// This migration does the following:
|
||||
/// - Adds "config_file_version = 2"
|
||||
/// - Introduce new key config_file_version, [tor.bridge] and [tor.proxy]
|
||||
/// - Migrate old config key/value and comments while it does not conflict with newly indroduced key and comments
|
||||
fn migrate_config_file_version_none_to_2(
|
||||
config_str: String,
|
||||
config_file_path: PathBuf,
|
||||
) -> Result<String, ConfigError> {
|
||||
let config: GlobalWalletConfigMembers =
|
||||
toml::from_str(&GlobalWalletConfig::fix_warning_level(config_str.clone())).unwrap();
|
||||
if config.config_file_version != None {
|
||||
return Ok(config_str);
|
||||
}
|
||||
let adjusted_config = GlobalWalletConfigMembers {
|
||||
config_file_version: GlobalWalletConfigMembers::default().config_file_version,
|
||||
tor: Some(TorConfig {
|
||||
bridge: TorBridgeConfig::default(),
|
||||
proxy: TorProxyConfig::default(),
|
||||
..config.tor.unwrap_or(TorConfig::default())
|
||||
}),
|
||||
..config
|
||||
};
|
||||
let mut gc = GlobalWalletConfig {
|
||||
members: Some(adjusted_config),
|
||||
config_file_path: Some(config_file_path.clone()),
|
||||
};
|
||||
let str_path = config_file_path.into_os_string().into_string().unwrap();
|
||||
gc.write_to_file(
|
||||
&str_path,
|
||||
true,
|
||||
Some(config_str),
|
||||
config.config_file_version,
|
||||
)?;
|
||||
let adjusted_config_str = fs::read_to_string(str_path.clone())?;
|
||||
Ok(adjusted_config_str)
|
||||
}
|
||||
|
||||
// For forwards compatibility old config needs `Warning` log level changed to standard log::Level `WARN`
|
||||
fn fix_warning_level(conf: String) -> String {
|
||||
|
|
|
@ -153,6 +153,15 @@ impl fmt::Display for ConfigError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for ConfigError {
|
||||
fn from(error: io::Error) -> ConfigError {
|
||||
ConfigError::FileIOError(
|
||||
String::from(""),
|
||||
format!("Error loading config file: {}", error),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tor configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TorConfig {
|
||||
|
@ -164,6 +173,12 @@ pub struct TorConfig {
|
|||
pub socks_proxy_addr: String,
|
||||
/// Send configuration directory
|
||||
pub send_config_dir: String,
|
||||
/// tor bridge config
|
||||
#[serde(default)]
|
||||
pub bridge: TorBridgeConfig,
|
||||
/// tor proxy config
|
||||
#[serde(default)]
|
||||
pub proxy: TorProxyConfig,
|
||||
}
|
||||
|
||||
impl Default for TorConfig {
|
||||
|
@ -173,15 +188,66 @@ impl Default for TorConfig {
|
|||
use_tor_listener: true,
|
||||
socks_proxy_addr: "127.0.0.1:59050".to_owned(),
|
||||
send_config_dir: ".".into(),
|
||||
bridge: TorBridgeConfig::default(),
|
||||
proxy: TorProxyConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<io::Error> for ConfigError {
|
||||
fn from(error: io::Error) -> ConfigError {
|
||||
ConfigError::FileIOError(
|
||||
String::from(""),
|
||||
format!("Error loading config file: {}", error),
|
||||
)
|
||||
|
||||
/// Tor Bridge Config
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TorBridgeConfig {
|
||||
/// Bridge Line
|
||||
pub bridge_line: Option<String>,
|
||||
/// Client Option
|
||||
pub client_option: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for TorBridgeConfig {
|
||||
fn default() -> TorBridgeConfig {
|
||||
TorBridgeConfig {
|
||||
bridge_line: None,
|
||||
client_option: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TorBridgeConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tor Proxy configuration (useful for protocols such as shadowsocks)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TorProxyConfig {
|
||||
/// socks4 |socks5 | http(s)
|
||||
pub transport: Option<String>,
|
||||
/// ip or dns
|
||||
pub address: Option<String>,
|
||||
/// user for auth - socks5|https(s)
|
||||
pub username: Option<String>,
|
||||
/// pass for auth - socks5|https(s)
|
||||
pub password: Option<String>,
|
||||
/// allowed port - proxy
|
||||
pub allowed_port: Option<Vec<u16>>,
|
||||
}
|
||||
|
||||
impl Default for TorProxyConfig {
|
||||
fn default() -> TorProxyConfig {
|
||||
TorProxyConfig {
|
||||
transport: None,
|
||||
address: None,
|
||||
username: None,
|
||||
password: None,
|
||||
allowed_port: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TorProxyConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,6 +263,9 @@ pub struct GlobalWalletConfig {
|
|||
/// Wallet internal members
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct GlobalWalletConfigMembers {
|
||||
/// Config file version (None == version 1)
|
||||
#[serde(default)]
|
||||
pub config_file_version: Option<u32>,
|
||||
/// Wallet configuration
|
||||
#[serde(default)]
|
||||
pub wallet: WalletConfig,
|
||||
|
|
|
@ -334,6 +334,7 @@ pub struct SendArgs {
|
|||
pub ttl_blocks: Option<u64>,
|
||||
pub skip_tor: bool,
|
||||
pub outfile: Option<String>,
|
||||
pub bridge: Option<String>,
|
||||
}
|
||||
|
||||
pub fn send<L, C, K>(
|
||||
|
@ -412,6 +413,9 @@ where
|
|||
|
||||
let tor_config = match tor_config {
|
||||
Some(mut c) => {
|
||||
if let Some(b) = args.bridge.clone() {
|
||||
c.bridge.bridge_line = Some(b);
|
||||
}
|
||||
c.skip_send_attempt = Some(args.skip_tor);
|
||||
Some(c)
|
||||
}
|
||||
|
@ -605,6 +609,7 @@ pub struct ReceiveArgs {
|
|||
pub input_slatepack_message: Option<String>,
|
||||
pub skip_tor: bool,
|
||||
pub outfile: Option<String>,
|
||||
pub bridge: Option<String>,
|
||||
}
|
||||
|
||||
pub fn receive<L, C, K>(
|
||||
|
@ -634,6 +639,9 @@ where
|
|||
|
||||
let tor_config = match tor_config {
|
||||
Some(mut c) => {
|
||||
if let Some(b) = args.bridge {
|
||||
c.bridge.bridge_line = Some(b);
|
||||
}
|
||||
c.skip_send_attempt = Some(args.skip_tor);
|
||||
Some(c)
|
||||
}
|
||||
|
@ -889,6 +897,7 @@ pub struct ProcessInvoiceArgs {
|
|||
pub ttl_blocks: Option<u64>,
|
||||
pub skip_tor: bool,
|
||||
pub outfile: Option<String>,
|
||||
pub bridge: Option<String>,
|
||||
}
|
||||
|
||||
/// Process invoice
|
||||
|
@ -965,6 +974,9 @@ where
|
|||
|
||||
let tor_config = match tor_config {
|
||||
Some(mut c) => {
|
||||
if let Some(b) = args.bridge {
|
||||
c.bridge.bridge_line = Some(b);
|
||||
}
|
||||
c.skip_send_attempt = Some(args.skip_tor);
|
||||
Some(c)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ use crate::util::secp::key::SecretKey;
|
|||
use crate::util::{from_hex, static_secp_instance, to_base64, Mutex};
|
||||
use failure::ResultExt;
|
||||
use grin_wallet_api::JsonId;
|
||||
use grin_wallet_config::types::{TorBridgeConfig, TorProxyConfig};
|
||||
use grin_wallet_util::OnionV3Address;
|
||||
use hyper::body;
|
||||
use hyper::header::HeaderValue;
|
||||
|
@ -38,6 +39,7 @@ use std::sync::Arc;
|
|||
|
||||
use crate::impls::tor::config as tor_config;
|
||||
use crate::impls::tor::process as tor_process;
|
||||
use crate::impls::tor::{bridge as tor_bridge, proxy as tor_proxy};
|
||||
|
||||
use crate::apiwallet::{
|
||||
EncryptedRequest, EncryptedResponse, EncryptionErrorResponse, Foreign,
|
||||
|
@ -83,6 +85,8 @@ fn init_tor_listener<L, C, K>(
|
|||
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
|
||||
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
|
||||
addr: &str,
|
||||
bridge: TorBridgeConfig,
|
||||
tor_proxy: TorProxyConfig,
|
||||
) -> Result<(tor_process::TorProcess, SlatepackAddress), Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K> + 'static,
|
||||
|
@ -103,17 +107,44 @@ where
|
|||
let onion_address = OnionV3Address::from_private(&sec_key.0)
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
let sp_address = SlatepackAddress::try_from(onion_address.clone())?;
|
||||
|
||||
let mut hm_tor_bridge: HashMap<String, String> = HashMap::new();
|
||||
let mut tor_timeout = 20;
|
||||
if bridge.bridge_line.is_some() {
|
||||
tor_timeout = 40;
|
||||
let bridge_config = tor_bridge::TorBridge::try_from(bridge)
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{}", e).into()))?;
|
||||
hm_tor_bridge = bridge_config
|
||||
.to_hashmap()
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{}", e).into()))?;
|
||||
}
|
||||
|
||||
let mut hm_tor_poxy: HashMap<String, String> = HashMap::new();
|
||||
if tor_proxy.transport.is_some() || tor_proxy.allowed_port.is_some() {
|
||||
let proxy_config = tor_proxy::TorProxy::try_from(tor_proxy)
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{}", e).into()))?;
|
||||
hm_tor_poxy = proxy_config
|
||||
.to_hashmap()
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{}", e.kind()).into()))?;
|
||||
}
|
||||
|
||||
warn!(
|
||||
"Starting Tor Hidden Service for API listener at address {}, binding to {}",
|
||||
onion_address, addr
|
||||
);
|
||||
tor_config::output_tor_listener_config(&tor_dir, addr, &vec![sec_key])
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
tor_config::output_tor_listener_config(
|
||||
&tor_dir,
|
||||
addr,
|
||||
&vec![sec_key],
|
||||
hm_tor_bridge,
|
||||
hm_tor_poxy,
|
||||
)
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
// Start TOR process
|
||||
process
|
||||
.torrc_path(&format!("{}/torrc", tor_dir))
|
||||
.working_dir(&tor_dir)
|
||||
.timeout(20)
|
||||
.timeout(tor_timeout)
|
||||
.completion_percent(100)
|
||||
.launch()
|
||||
.map_err(|e| ErrorKind::TorProcess(format!("{:?}", e).into()))?;
|
||||
|
@ -266,17 +297,31 @@ where
|
|||
let lc = w_lock.lc_provider()?;
|
||||
let _ = lc.wallet_inst()?;
|
||||
}
|
||||
|
||||
let (tor_bridge, tor_proxy) = match tor_config.clone() {
|
||||
Some(s) => (s.bridge, s.proxy),
|
||||
None => (TorBridgeConfig::default(), TorProxyConfig::default()),
|
||||
};
|
||||
|
||||
// need to keep in scope while the main listener is running
|
||||
let (_tor_process, address) = match use_tor {
|
||||
true => match init_tor_listener(wallet.clone(), keychain_mask.clone(), addr) {
|
||||
Ok((tp, addr)) => (Some(tp), Some(addr)),
|
||||
Err(e) => {
|
||||
warn!("Unable to start TOR listener; Check that TOR executable is installed and on your path");
|
||||
warn!("Tor Error: {}", e);
|
||||
warn!("Listener will be available via HTTP only");
|
||||
(None, None)
|
||||
true => {
|
||||
match init_tor_listener(
|
||||
wallet.clone(),
|
||||
keychain_mask.clone(),
|
||||
addr,
|
||||
tor_bridge,
|
||||
tor_proxy,
|
||||
) {
|
||||
Ok((tp, addr)) => (Some(tp), Some(addr)),
|
||||
Err(e) => {
|
||||
warn!("Unable to start TOR listener; Check that TOR executable is installed and on your path");
|
||||
error!("Tor Error: {}", e);
|
||||
warn!("Listener will be available via HTTP only");
|
||||
(None, None)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
false => (None, None),
|
||||
};
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ lazy_static = "1"
|
|||
tokio = { version = "0.2", features = ["full"] }
|
||||
reqwest = { version = "0.10", features = ["rustls-tls", "socks"] }
|
||||
|
||||
#Socks/Tor
|
||||
#Socks/Tor/Bridge/Proxy
|
||||
byteorder = "1"
|
||||
ed25519-dalek = "1.0.0-pre.4"
|
||||
x25519-dalek = "0.6"
|
||||
|
@ -34,6 +34,8 @@ data-encoding = "2"
|
|||
regex = "1.3"
|
||||
timer = "0.2"
|
||||
sysinfo = "0.14"
|
||||
base64 = "0.12.0"
|
||||
url = "2.1"
|
||||
|
||||
grin_wallet_util = { path = "../util", version = "5.1.0-alpha.1" }
|
||||
grin_wallet_config = { path = "../config", version = "5.1.0-alpha.1" }
|
||||
|
|
|
@ -16,9 +16,14 @@
|
|||
use crate::client_utils::{Client, ClientError, ClientErrorKind};
|
||||
use crate::libwallet::slate_versions::{SlateVersion, VersionedSlate};
|
||||
use crate::libwallet::{Error, ErrorKind, Slate};
|
||||
use crate::tor::bridge::TorBridge;
|
||||
use crate::tor::proxy::TorProxy;
|
||||
use crate::SlateSender;
|
||||
use grin_wallet_config::types::{TorBridgeConfig, TorProxyConfig};
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
use std::sync::Arc;
|
||||
|
@ -35,6 +40,8 @@ pub struct HttpSlateSender {
|
|||
socks_proxy_addr: Option<SocketAddr>,
|
||||
tor_config_dir: String,
|
||||
process: Option<Arc<tor_process::TorProcess>>,
|
||||
bridge: TorBridgeConfig,
|
||||
proxy: TorProxyConfig,
|
||||
}
|
||||
|
||||
impl HttpSlateSender {
|
||||
|
@ -49,6 +56,8 @@ impl HttpSlateSender {
|
|||
socks_proxy_addr: None,
|
||||
tor_config_dir: String::from(""),
|
||||
process: None,
|
||||
bridge: TorBridgeConfig::default(),
|
||||
proxy: TorProxyConfig::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -58,12 +67,16 @@ impl HttpSlateSender {
|
|||
base_url: &str,
|
||||
proxy_addr: &str,
|
||||
tor_config_dir: &str,
|
||||
tor_bridge: TorBridgeConfig,
|
||||
tor_proxy: TorProxyConfig,
|
||||
) -> Result<HttpSlateSender, SchemeNotHttp> {
|
||||
let mut ret = Self::new(base_url)?;
|
||||
ret.use_socks = true;
|
||||
//TODO: Unwrap
|
||||
ret.socks_proxy_addr = Some(SocketAddr::V4(proxy_addr.parse().unwrap()));
|
||||
ret.tor_config_dir = tor_config_dir.into();
|
||||
ret.bridge = tor_bridge;
|
||||
ret.proxy = tor_proxy;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
|
@ -80,9 +93,30 @@ impl HttpSlateSender {
|
|||
"Starting TOR Process for send at {:?}",
|
||||
self.socks_proxy_addr
|
||||
);
|
||||
|
||||
let mut hm_tor_bridge: HashMap<String, String> = HashMap::new();
|
||||
if self.bridge.bridge_line.is_some() {
|
||||
let bridge_struct = TorBridge::try_from(self.bridge.clone())
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
hm_tor_bridge = bridge_struct
|
||||
.to_hashmap()
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
}
|
||||
|
||||
let mut hm_tor_proxy: HashMap<String, String> = HashMap::new();
|
||||
if self.proxy.transport.is_some() || self.proxy.allowed_port.is_some() {
|
||||
let proxy = TorProxy::try_from(self.proxy.clone())
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
hm_tor_proxy = proxy
|
||||
.to_hashmap()
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
}
|
||||
|
||||
tor_config::output_tor_sender_config(
|
||||
&tor_dir,
|
||||
&self.socks_proxy_addr.unwrap().to_string(),
|
||||
hm_tor_bridge,
|
||||
hm_tor_proxy,
|
||||
)
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e)))?;
|
||||
// Start TOR process
|
||||
|
|
|
@ -47,6 +47,14 @@ pub enum ErrorKind {
|
|||
#[fail(display = "Onion V3 Address Error")]
|
||||
OnionV3Address(OnionV3AddressError),
|
||||
|
||||
/// Error when obfs4proxy is not in the user path if TOR brigde is enabled
|
||||
#[fail(display = "Unable to find obfs4proxy binary in your path; {}", _0)]
|
||||
Obfs4proxyBin(String),
|
||||
|
||||
/// Error the bridge input is in bad format
|
||||
#[fail(display = "Bridge line is in bad format; {}", _0)]
|
||||
BridgeLine(String),
|
||||
|
||||
/// Error when formatting json
|
||||
#[fail(display = "IO error")]
|
||||
IO,
|
||||
|
@ -83,6 +91,14 @@ pub enum ErrorKind {
|
|||
#[fail(display = "{}", _0)]
|
||||
ArgumentError(String),
|
||||
|
||||
/// Tor Bridge error
|
||||
#[fail(display = "Tor Bridge Error: {}", _0)]
|
||||
TorBridge(String),
|
||||
|
||||
/// Tor Proxy error
|
||||
#[fail(display = "Tor Proxy Error: {}", _0)]
|
||||
TorProxy(String),
|
||||
|
||||
/// Generating ED25519 Public Key
|
||||
#[fail(display = "Error generating ed25519 secret key: {}", _0)]
|
||||
ED25519Key(String),
|
||||
|
|
|
@ -79,6 +79,10 @@ where
|
|||
tor_config: Option<TorConfig>,
|
||||
) -> Result<(), Error> {
|
||||
let mut default_config = GlobalWalletConfig::for_chain(&chain_type);
|
||||
let config_file_version = match default_config.members.as_ref() {
|
||||
Some(m) => m.clone().config_file_version,
|
||||
None => None,
|
||||
};
|
||||
let logging = match logging_config {
|
||||
Some(l) => Some(l),
|
||||
None => match default_config.members.as_ref() {
|
||||
|
@ -102,6 +106,7 @@ where
|
|||
};
|
||||
default_config = GlobalWalletConfig {
|
||||
members: Some(GlobalWalletConfigMembers {
|
||||
config_file_version,
|
||||
wallet,
|
||||
tor,
|
||||
logging,
|
||||
|
@ -139,7 +144,8 @@ where
|
|||
abs_path.push(self.data_dir.clone());
|
||||
|
||||
default_config.update_paths(&abs_path);
|
||||
let res = default_config.write_to_file(config_file_name.to_str().unwrap());
|
||||
let res =
|
||||
default_config.write_to_file(config_file_name.to_str().unwrap(), false, None, None);
|
||||
if let Err(e) = res {
|
||||
let msg = format!(
|
||||
"Error creating config file as ({}): {}",
|
||||
|
|
661
impls/src/tor/bridge.rs
Normal file
661
impls/src/tor/bridge.rs
Normal file
|
@ -0,0 +1,661 @@
|
|||
// 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, ErrorKind};
|
||||
use base64;
|
||||
use grin_wallet_config::types::TorBridgeConfig;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::net::SocketAddr;
|
||||
use std::{env, str};
|
||||
use url::{Host, Url};
|
||||
|
||||
use crate::tor::proxy::TorProxy;
|
||||
|
||||
#[cfg(windows)]
|
||||
const OBFS4_EXE_NAME: &str = "obfs4proxy.exe";
|
||||
#[cfg(not(windows))]
|
||||
const OBFS4_EXE_NAME: &str = "obfs4proxy";
|
||||
|
||||
#[cfg(windows)]
|
||||
const SNOWFLAKE_EXE_NAME: &str = "snowflake-client.exe";
|
||||
#[cfg(not(windows))]
|
||||
const SNOWFLAKE_EXE_NAME: &str = "snowflake-client";
|
||||
|
||||
pub struct FlagParser<'a> {
|
||||
/// line left to be parsed
|
||||
line: &'a str,
|
||||
/// all flags, bool flags and flags that takes a value
|
||||
flags: Vec<&'a str>,
|
||||
/// bool flags, present in the client line
|
||||
bool_flags: Vec<&'a str>,
|
||||
/// is current parsed flag is a bool
|
||||
is_bool_flag: bool,
|
||||
// parsing client or bridge line
|
||||
client: bool,
|
||||
}
|
||||
|
||||
/// Flag parser, help to retrieve flags and it's value whether on the bridge or client option line
|
||||
impl<'a> FlagParser<'a> {
|
||||
pub fn new(line: &'a str, flags: Vec<&'a str>, bool_flags: Vec<&'a str>, client: bool) -> Self {
|
||||
Self {
|
||||
line,
|
||||
flags,
|
||||
bool_flags,
|
||||
is_bool_flag: false,
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
/// Used only on the client option line parsing, help to retrieve a known flags
|
||||
fn is_flag(&mut self) -> usize {
|
||||
let mut split_index = 0;
|
||||
let line = self.line.split_whitespace();
|
||||
self.is_bool_flag = false;
|
||||
for is_flag in line {
|
||||
let index = self.flags.iter().position(|&flag| flag == is_flag);
|
||||
if let Some(m) = index {
|
||||
let i = self.line.find(is_flag).unwrap();
|
||||
split_index = i + is_flag.len() + 1;
|
||||
let idx_b_flag = self
|
||||
.bool_flags
|
||||
.iter()
|
||||
.position(|&bool_flag| bool_flag == is_flag);
|
||||
if let Some(i) = idx_b_flag {
|
||||
self.is_bool_flag = true;
|
||||
self.bool_flags.remove(i);
|
||||
}
|
||||
self.flags.remove(m);
|
||||
return split_index;
|
||||
}
|
||||
}
|
||||
split_index
|
||||
}
|
||||
|
||||
/// Determine at which index we should take the value linked to its flags
|
||||
fn end(&mut self, is_bool_flag: bool, right: &str) -> usize {
|
||||
if is_bool_flag {
|
||||
0
|
||||
} else if right.starts_with('"') {
|
||||
right[1..].find('"').unwrap_or(0) + 2
|
||||
} else {
|
||||
right.find(' ').unwrap_or(right.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FlagParser<'a> {
|
||||
type Item = (&'a str, &'a str);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (left, right) = if self.client {
|
||||
// Client parser
|
||||
let split_index = self.is_flag();
|
||||
let (l, r) = self.line.split_at(split_index);
|
||||
(l, r)
|
||||
} else {
|
||||
// Bridge parser
|
||||
let split_index = self.line.find("=")?;
|
||||
let (l, r) = self.line.split_at(split_index + 1);
|
||||
(l, r)
|
||||
};
|
||||
let end = self.end(self.is_bool_flag, right);
|
||||
let key = left.split_whitespace().last()?;
|
||||
let val = &right[..end];
|
||||
self.line = &right[end..].trim();
|
||||
Some((key, val))
|
||||
}
|
||||
}
|
||||
|
||||
/// Every args field that could be in the bridge line
|
||||
/// obfs4 args : https://github.com/Yawning/obfs4/blob/40245c4a1cf221395c59d1f4bf274127045352f9/transports/obfs4/obfs4.go#L86-L91
|
||||
/// meek_lite args : https://github.com/Yawning/obfs4/blob/40245c4a1cf221395c59d1f4bf274127045352f9/transports/meeklite/meek.go#L93-L127
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Transport {
|
||||
/// transport type: obfs4, meek_lite, meek, snowflake
|
||||
pub transport: Option<String>,
|
||||
/// server address
|
||||
pub server: Option<String>,
|
||||
/// fingerprint
|
||||
pub fingerprint: Option<String>,
|
||||
/// certificate (obfs4)
|
||||
pub cert: Option<String>,
|
||||
/// IAT obfuscation: 0 disabled, 1 enabled, 2 paranoid (obfs4)
|
||||
pub iatmode: Option<String>,
|
||||
/// URL of signaling broker (meek)
|
||||
pub url: Option<String>,
|
||||
/// optional - front domain (meek)
|
||||
pub front: Option<String>,
|
||||
/// optional - URL of AMP cache to use as a proxy for signaling (meek)
|
||||
pub utls: Option<String>,
|
||||
/// optional - HPKP disable argument. (meek)
|
||||
pub disablehpkp: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Transport {
|
||||
fn default() -> Transport {
|
||||
Transport {
|
||||
transport: None,
|
||||
server: None,
|
||||
fingerprint: None,
|
||||
cert: None,
|
||||
iatmode: None,
|
||||
url: None,
|
||||
front: None,
|
||||
utls: None,
|
||||
disablehpkp: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transport {
|
||||
/// Parse the server address of the bridge line
|
||||
fn parse_socketaddr_arg(arg: Option<&&str>) -> Result<String, Error> {
|
||||
match arg {
|
||||
Some(addr) => {
|
||||
let address = addr.parse::<SocketAddr>().map_err(|_e| {
|
||||
ErrorKind::TorBridge(format!("Invalid bridge server address: {}", addr).into())
|
||||
})?;
|
||||
Ok(address.to_string())
|
||||
}
|
||||
None => {
|
||||
let msg = format!("Missing bridge server address");
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the fingerprint of the bridge line (obfs4/snowflake/meek)
|
||||
fn parse_fingerprint_arg(arg: Option<&&str>) -> Result<Option<String>, Error> {
|
||||
match arg {
|
||||
Some(f) => {
|
||||
let fgp = f.to_owned();
|
||||
let is_hex = fgp.chars().all(|c| c.is_ascii_hexdigit());
|
||||
let fingerprint = fgp.to_uppercase();
|
||||
if !(is_hex && fingerprint.len() == 40) {
|
||||
let msg = format!("Invalid fingerprint: {}", fingerprint);
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
Ok(Some(fingerprint))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
/// Parse the certificate of the bridge line (obfs4)
|
||||
pub fn parse_cert_arg(arg: &str) -> Result<String, Error> {
|
||||
let cert_vec = base64::decode(arg).map_err(|_e| {
|
||||
ErrorKind::TorBridge(format!(
|
||||
"Invalid certificate, error decoding bridge certificate: {}",
|
||||
arg
|
||||
))
|
||||
})?;
|
||||
if cert_vec.len() != 52 {
|
||||
let msg = format!("Invalid certificate: {}", arg);
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
Ok(arg.to_string())
|
||||
}
|
||||
/// Parse the iatmode of the bridge line (obfs4)
|
||||
pub fn parse_iatmode_arg(arg: &str) -> Result<String, Error> {
|
||||
let iatmode = arg.parse::<u8>().unwrap_or(0);
|
||||
if !((0..3).contains(&iatmode)) {
|
||||
let msg = format!("Invalid iatmode: {}, must be between 0 and 2", iatmode);
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
Ok(iatmode.to_string())
|
||||
}
|
||||
|
||||
/// Parse the max value for the arg -max in the client line option (snowflake)
|
||||
fn parse_hpkp_arg(arg: &str) -> Result<String, Error> {
|
||||
let max = arg.parse::<bool>().map_err(|_e| {
|
||||
ErrorKind::TorBridge(
|
||||
format!("Invalid -max value: {}, must be \"true\" or \"false\"", arg).into(),
|
||||
)
|
||||
})?;
|
||||
Ok(max.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// Client Plugin such as snowflake or obfs4proxy
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PluginClient {
|
||||
// Path plugin client
|
||||
pub path: Option<String>,
|
||||
// Plugin client option
|
||||
pub option: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for PluginClient {
|
||||
fn default() -> PluginClient {
|
||||
PluginClient {
|
||||
path: None,
|
||||
option: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginClient {
|
||||
/// Get the hashmap key(argument) and attached value of the client option line.
|
||||
pub fn get_flags(s: &str) -> HashMap<&str, &str> {
|
||||
let flags = vec![
|
||||
"-url",
|
||||
"-front",
|
||||
"-ice",
|
||||
"-log",
|
||||
"-log-to-state-dir",
|
||||
"-keep-local-addresses",
|
||||
"-unsafe-logging",
|
||||
"-max",
|
||||
"-loglevel",
|
||||
"-enableLogging",
|
||||
"-unsafeLogging",
|
||||
];
|
||||
let bool_flags = vec![
|
||||
"-log-to-state-dir",
|
||||
"-keep-local-addresses",
|
||||
"-unsafe-logging",
|
||||
"-enableLogging",
|
||||
"-unsafeLogging",
|
||||
];
|
||||
FlagParser::new(s, flags, bool_flags, true).collect()
|
||||
}
|
||||
|
||||
/// Try to find the plugin client path
|
||||
pub fn get_client_path(plugin: &str) -> Result<String, Error> {
|
||||
let plugin_path = env::var_os("PATH").and_then(|path| {
|
||||
env::split_paths(&path)
|
||||
.filter_map(|dir| {
|
||||
let full_path = dir.join(plugin);
|
||||
if full_path.is_file() {
|
||||
Some(full_path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next()
|
||||
});
|
||||
match plugin_path {
|
||||
Some(path) => Ok(path.into_os_string().into_string().unwrap()),
|
||||
None => {
|
||||
let msg = format!("Transport client \"{}\" is missing, make sure it's installed and on your path.", plugin);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the URL value for the arg -url in the client line option (snowflake)
|
||||
fn parse_url_arg(arg: &str) -> Result<String, Error> {
|
||||
let url = arg
|
||||
.parse::<Url>()
|
||||
.map_err(|_e| ErrorKind::TorBridge(format!("Invalid -url value: {}", arg).into()))?;
|
||||
Ok(url.to_string())
|
||||
}
|
||||
|
||||
/// Parse the DNS domain value for the arg -front in the client line option (snowflake)
|
||||
fn parse_front_arg(arg: &str) -> Result<String, Error> {
|
||||
let front = Host::parse(arg).map_err(|_e| {
|
||||
ErrorKind::TorBridge(format!("Invalid -front hostname value: {}", arg).into())
|
||||
})?;
|
||||
match front {
|
||||
Host::Domain(_) => Ok(front.to_string()),
|
||||
Host::Ipv4(_) | Host::Ipv6(_) => {
|
||||
let msg = format!(
|
||||
"Invalid front argument: {}, in the client option. Must be a DNS Domain",
|
||||
front
|
||||
);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the ICE address value for the arg -ice in the client line option (snowflake)
|
||||
fn parse_ice_arg(arg: &str) -> Result<String, Error> {
|
||||
let ice_addr = arg.trim();
|
||||
let vec_ice_addr = ice_addr.split(",");
|
||||
for addr in vec_ice_addr {
|
||||
let addr = addr.to_lowercase();
|
||||
if addr.starts_with("stun:") || addr.starts_with("turn:") {
|
||||
let address = addr.replace("stun:", "").replace("turn:", "");
|
||||
let _p_address = TorProxy::parse_address(&address)
|
||||
.map_err(|e| ErrorKind::TorBridge(format!("{}", e.kind()).into()))?;
|
||||
} else {
|
||||
let msg = format!(
|
||||
"Invalid ICE address: {}. Must be a stun or turn address",
|
||||
addr
|
||||
);
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
}
|
||||
Ok(ice_addr.to_string())
|
||||
}
|
||||
|
||||
/// Parse the max value for the arg -max in the client line option (snowflake)
|
||||
fn parse_max_arg(arg: &str) -> Result<String, Error> {
|
||||
match arg.parse::<u16>() {
|
||||
Ok(max) => Ok(max.to_string()),
|
||||
Err(_e) => {
|
||||
let msg = format!("Invalid -max argument: {} in the client option.", arg);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the loglevel value for the arg -loglevel in the client line option (obfs4)
|
||||
fn parse_loglevel_arg(arg: &str) -> Result<String, Error> {
|
||||
let log_level = arg.to_uppercase();
|
||||
match log_level.as_str() {
|
||||
"ERROR" | "WARN" | "INFO" | "DEBUG" => Ok(log_level.to_string()),
|
||||
_ => {
|
||||
let msg = format!("Invalid log level argurment: {}, in the client option. Must be: ERROR, WARN, INFO or DEBUG", log_level);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse and verify if the client option line of obfs4proxy or snowflake are correct
|
||||
/// Obfs4proxy client args : https://github.com/Yawning/obfs4/blob/40245c4a1cf221395c59d1f4bf274127045352f9/obfs4proxy/obfs4proxy.go#L313-L316
|
||||
/// Snowflake client args : https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/blob/main/client/snowflake.go#L123-132
|
||||
pub fn parse_client(option: &str, snowflake: bool) -> Result<String, Error> {
|
||||
let hm_flags = PluginClient::get_flags(option);
|
||||
let mut string = String::from("");
|
||||
if snowflake {
|
||||
let (ck_url, ck_ice) = (hm_flags.contains_key("-url"), hm_flags.contains_key("-ice"));
|
||||
if !(ck_url || ck_ice) {
|
||||
let msg = if !ck_url {
|
||||
format!("Missing URL argurment for snowflake transport, specify \"-url\"")
|
||||
} else {
|
||||
format!("Missing ICE argurment for snowflake transport, specify \"-ice\"")
|
||||
};
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
for (key, value) in hm_flags {
|
||||
let p_value = match key {
|
||||
"-url" => PluginClient::parse_url_arg(value)?,
|
||||
"-front" => PluginClient::parse_front_arg(value)?,
|
||||
"-ice" => PluginClient::parse_ice_arg(value)?,
|
||||
"-ampcache" => value.to_string(),
|
||||
"-log" => value.to_string(),
|
||||
"-log-to-state-dir" => String::from(""),
|
||||
"-keep-local-addresses" => String::from(""),
|
||||
"-unsafe-logging" => String::from(""),
|
||||
"-max" => PluginClient::parse_max_arg(value)?,
|
||||
_ => continue,
|
||||
};
|
||||
string.push_str(format!(" {} {}", key, p_value).trim_end())
|
||||
}
|
||||
} else {
|
||||
for (key, value) in hm_flags {
|
||||
let p_value = match key {
|
||||
"-loglevel" => PluginClient::parse_loglevel_arg(value)?,
|
||||
"-enableLogging" => String::from(""),
|
||||
"-unsafeLogging" => String::from(""),
|
||||
_ => continue,
|
||||
};
|
||||
string.push_str(format!(" {} {}", key, p_value).trim_end())
|
||||
}
|
||||
}
|
||||
let p_string = string.trim_start().to_string();
|
||||
Ok(p_string)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tor Bridge Field
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TorBridge {
|
||||
/// tor bridge (transport field)
|
||||
pub bridge: Transport,
|
||||
// tor bridge plugin client (path and option)
|
||||
pub client: PluginClient,
|
||||
}
|
||||
|
||||
impl Default for TorBridge {
|
||||
fn default() -> TorBridge {
|
||||
TorBridge {
|
||||
bridge: Transport::default(),
|
||||
client: PluginClient::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TorBridge {
|
||||
/// Get the hashmap key(argument) and attached value of the bridge line. r
|
||||
pub fn get_flags(s: &str) -> HashMap<&str, &str> {
|
||||
FlagParser::new(s, vec![], vec![], false).collect()
|
||||
}
|
||||
|
||||
/// Bridge and client option convertion to hashmap, facility for the writing of the torrc config
|
||||
pub fn to_hashmap(&self) -> Result<HashMap<String, String>, Error> {
|
||||
let bridge = self.bridge.clone();
|
||||
let client = self.client.clone();
|
||||
let transport = bridge.transport.as_ref().unwrap().as_str();
|
||||
let mut ret_val = HashMap::new();
|
||||
match transport {
|
||||
"obfs4" => {
|
||||
let string_un = &String::from("");
|
||||
let chskey = "ClientTransportPlugin".to_string();
|
||||
let chsvalue = format!(
|
||||
"{} exec {} {}",
|
||||
transport,
|
||||
client.path.as_ref().unwrap(),
|
||||
client.option.as_ref().unwrap_or(string_un)
|
||||
);
|
||||
ret_val.insert(chskey, chsvalue);
|
||||
|
||||
let hskey = "Bridge".to_string();
|
||||
let mut hsvalue = format!("{} {}", transport, bridge.server.as_ref().unwrap());
|
||||
if let Some(fingerprint) = bridge.fingerprint {
|
||||
hsvalue.push_str(format!(" {}", fingerprint).as_str())
|
||||
}
|
||||
hsvalue.push_str(format!(" cert={}", bridge.cert.unwrap()).as_str());
|
||||
hsvalue.push_str(format!(" iat-mode={}", bridge.iatmode.unwrap()).as_str());
|
||||
ret_val.insert(hskey, hsvalue);
|
||||
|
||||
Ok(ret_val)
|
||||
}
|
||||
|
||||
"meek_lite" => {
|
||||
let chskey = "ClientTransportPlugin".to_string();
|
||||
let mut chsvalue = format!("{} exec {}", transport, client.path.as_ref().unwrap());
|
||||
if let Some(option) = client.option {
|
||||
chsvalue.push_str(format!(" {}", option).as_str())
|
||||
}
|
||||
ret_val.insert(chskey, chsvalue);
|
||||
|
||||
let hskey = "Bridge".to_string();
|
||||
let mut hsvalue = format!("{} {}", transport, bridge.server.as_ref().unwrap());
|
||||
if let Some(fingerprint) = bridge.fingerprint {
|
||||
hsvalue.push_str(format!(" {}", fingerprint).as_str())
|
||||
}
|
||||
|
||||
hsvalue.push_str(format!(" url={}", bridge.url.as_ref().unwrap()).as_str());
|
||||
|
||||
if let Some(front) = bridge.front {
|
||||
hsvalue.push_str(format!(" front={}", front).as_str())
|
||||
}
|
||||
if let Some(utls) = bridge.utls {
|
||||
hsvalue.push_str(format!(" utls={}", utls).as_str())
|
||||
}
|
||||
if let Some(disablehpkp) = bridge.disablehpkp {
|
||||
hsvalue.push_str(format!(" disableHPKP={}", disablehpkp).as_str())
|
||||
}
|
||||
ret_val.insert(hskey, hsvalue);
|
||||
Ok(ret_val)
|
||||
}
|
||||
|
||||
"snowflake" => {
|
||||
let chskey = "ClientTransportPlugin".to_string();
|
||||
let chsvalue = format!(
|
||||
"{} exec {} {}",
|
||||
transport,
|
||||
client.path.as_ref().unwrap(),
|
||||
client.option.as_ref().unwrap()
|
||||
);
|
||||
ret_val.insert(chskey, chsvalue);
|
||||
|
||||
let hskey = "Bridge".to_string();
|
||||
let mut hsvalue = format!("{} {}", transport, bridge.server.as_ref().unwrap());
|
||||
if let Some(fingerprint) = bridge.fingerprint {
|
||||
hsvalue.push_str(format!(" {}", fingerprint).as_str())
|
||||
}
|
||||
ret_val.insert(hskey, hsvalue);
|
||||
Ok(ret_val)
|
||||
}
|
||||
|
||||
_ => {
|
||||
let msg = format!(
|
||||
"Invalid transport method: {} - must be obfs4/meek_lite/meek/snowflake",
|
||||
transport
|
||||
);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TorBridgeConfig> for TorBridge {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(tbc: TorBridgeConfig) -> Result<Self, Self::Error> {
|
||||
let bridge = match tbc.bridge_line {
|
||||
Some(b) => b,
|
||||
None => return Ok(TorBridge::default()),
|
||||
};
|
||||
let flags = TorBridge::get_flags(&bridge);
|
||||
let split = bridge.split_whitespace().collect::<Vec<&str>>();
|
||||
let mut iter = split.iter();
|
||||
let transport = iter.next().unwrap().to_lowercase();
|
||||
match transport.as_str() {
|
||||
"obfs4" => {
|
||||
let socketaddr = Transport::parse_socketaddr_arg(iter.next())?;
|
||||
let fingerprint = Transport::parse_fingerprint_arg(iter.next())?;
|
||||
let cert = match flags.get_key_value("cert=") {
|
||||
Some(hm) => Transport::parse_cert_arg(hm.1)?,
|
||||
None => {
|
||||
let msg =
|
||||
format!("Missing cert argurment in obfs4 transport, specify \"cert=\"");
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
};
|
||||
let iatmode = match flags.get_key_value("iat-mode=") {
|
||||
Some(hm) => Transport::parse_iatmode_arg(hm.1)?,
|
||||
None => String::from("0"),
|
||||
};
|
||||
let path = PluginClient::get_client_path(OBFS4_EXE_NAME)?;
|
||||
let option = match tbc.client_option {
|
||||
Some(o) => Some(PluginClient::parse_client(&o, false)?),
|
||||
None => None,
|
||||
};
|
||||
let tbpc = TorBridge {
|
||||
bridge: Transport {
|
||||
transport: Some("obfs4".into()),
|
||||
server: Some(socketaddr.to_string()),
|
||||
fingerprint: fingerprint,
|
||||
cert: Some(cert.into()),
|
||||
iatmode: Some(iatmode),
|
||||
..Transport::default()
|
||||
},
|
||||
client: PluginClient {
|
||||
path: Some(path),
|
||||
option: option,
|
||||
},
|
||||
};
|
||||
Ok(tbpc)
|
||||
}
|
||||
|
||||
"meek_lite" | "meek" => {
|
||||
let socketaddr = Transport::parse_socketaddr_arg(iter.next())?;
|
||||
let fingerprint = Transport::parse_fingerprint_arg(iter.next())?;
|
||||
let url = match flags.get_key_value("url=") {
|
||||
Some(hm) => PluginClient::parse_url_arg(hm.1)?,
|
||||
None => {
|
||||
let msg = format!(
|
||||
"Missing url argurment in meek_lite transport, specify \"url=\""
|
||||
);
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
};
|
||||
let front = match flags.get_key_value("front=") {
|
||||
Some(hm) => Some(PluginClient::parse_front_arg(hm.1)?),
|
||||
None => None,
|
||||
};
|
||||
let utls = match flags.get_key_value("utls=") {
|
||||
Some(hm) => Some(hm.1.to_string()),
|
||||
None => None,
|
||||
};
|
||||
let disablehpkp = match flags.get_key_value("disablehpkp=") {
|
||||
Some(hm) => Some(Transport::parse_hpkp_arg(hm.1)?),
|
||||
None => None,
|
||||
};
|
||||
let path = PluginClient::get_client_path(OBFS4_EXE_NAME)?;
|
||||
let option = match tbc.client_option {
|
||||
Some(o) => Some(PluginClient::parse_client(&o, false)?),
|
||||
None => None,
|
||||
};
|
||||
let tbpc = TorBridge {
|
||||
bridge: Transport {
|
||||
transport: Some("meek_lite".into()),
|
||||
server: Some(socketaddr.to_string()),
|
||||
fingerprint: fingerprint,
|
||||
url: Some(url),
|
||||
front: front,
|
||||
utls: utls,
|
||||
disablehpkp: disablehpkp,
|
||||
..Transport::default()
|
||||
},
|
||||
client: PluginClient {
|
||||
path: Some(path),
|
||||
option: option,
|
||||
},
|
||||
};
|
||||
Ok(tbpc)
|
||||
}
|
||||
|
||||
"snowflake" => {
|
||||
let socketaddr = Transport::parse_socketaddr_arg(iter.next())?;
|
||||
let fingerprint = Transport::parse_fingerprint_arg(iter.next())?;
|
||||
let path = PluginClient::get_client_path(SNOWFLAKE_EXE_NAME)?;
|
||||
let option = match tbc.client_option {
|
||||
Some(o) => PluginClient::parse_client(&o, true)?,
|
||||
None => {
|
||||
let url =
|
||||
"-url https://snowflake-broker.torproject.net.global.prod.fastly.net/";
|
||||
let front = "-front cdn.sstatic.net";
|
||||
let ice = "-ice stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478";
|
||||
format!("{} {} {}", url, front, ice)
|
||||
}
|
||||
};
|
||||
let tbpc = TorBridge {
|
||||
bridge: Transport {
|
||||
transport: Some("snowflake".into()),
|
||||
server: Some(socketaddr.to_string()),
|
||||
fingerprint: fingerprint,
|
||||
..Transport::default()
|
||||
},
|
||||
client: PluginClient {
|
||||
path: Some(path),
|
||||
option: Some(option),
|
||||
},
|
||||
};
|
||||
Ok(tbpc)
|
||||
}
|
||||
_ => {
|
||||
let msg = format!(
|
||||
"Invalid transport method: {} - must be obfs4/meek_lite/meek/snowflake",
|
||||
transport
|
||||
);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,11 +20,12 @@ use grin_wallet_util::OnionV3Address;
|
|||
use ed25519_dalek::ExpandedSecretKey;
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::{Path, MAIN_SEPARATOR};
|
||||
use std::string::String;
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
|
@ -171,6 +172,8 @@ pub fn output_torrc(
|
|||
wallet_listener_addr: &str,
|
||||
socks_port: &str,
|
||||
service_dirs: &[String],
|
||||
hm_tor_bridge: HashMap<String, String>,
|
||||
hm_tor_proxy: HashMap<String, String>,
|
||||
) -> Result<(), Error> {
|
||||
let torrc_file_path = format!("{}{}{}", tor_config_directory, MAIN_SEPARATOR, TORRC_FILE);
|
||||
|
||||
|
@ -186,6 +189,19 @@ pub fn output_torrc(
|
|||
props.add_item("HiddenServicePort", &format!("80 {}", wallet_listener_addr));
|
||||
}
|
||||
|
||||
if !hm_tor_bridge.is_empty() {
|
||||
props.add_item("UseBridges", "1");
|
||||
for (key, value) in hm_tor_bridge {
|
||||
props.add_item(&key, &value);
|
||||
}
|
||||
}
|
||||
|
||||
if !hm_tor_proxy.is_empty() {
|
||||
for (key, value) in hm_tor_proxy {
|
||||
props.add_item(&key, &value);
|
||||
}
|
||||
}
|
||||
|
||||
props.write_to_file(&torrc_file_path)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -196,6 +212,8 @@ pub fn output_tor_listener_config(
|
|||
tor_config_directory: &str,
|
||||
wallet_listener_addr: &str,
|
||||
listener_keys: &[SecretKey],
|
||||
hm_tor_bridge: HashMap<String, String>,
|
||||
hm_tor_proxy: HashMap<String, String>,
|
||||
) -> Result<(), Error> {
|
||||
let tor_data_dir = format!("{}{}{}", tor_config_directory, MAIN_SEPARATOR, TOR_DATA_DIR);
|
||||
|
||||
|
@ -215,6 +233,8 @@ pub fn output_tor_listener_config(
|
|||
wallet_listener_addr,
|
||||
"0",
|
||||
&service_dirs,
|
||||
hm_tor_bridge,
|
||||
hm_tor_proxy,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -224,11 +244,20 @@ pub fn output_tor_listener_config(
|
|||
pub fn output_tor_sender_config(
|
||||
tor_config_dir: &str,
|
||||
socks_listener_addr: &str,
|
||||
hm_tor_bridge: HashMap<String, String>,
|
||||
hm_tor_proxy: HashMap<String, String>,
|
||||
) -> Result<(), Error> {
|
||||
// create data directory if it doesn't exist
|
||||
fs::create_dir_all(&tor_config_dir).context(ErrorKind::IO)?;
|
||||
|
||||
output_torrc(tor_config_dir, "", socks_listener_addr, &[])?;
|
||||
output_torrc(
|
||||
tor_config_dir,
|
||||
"",
|
||||
socks_listener_addr,
|
||||
&[],
|
||||
hm_tor_bridge,
|
||||
hm_tor_proxy,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -293,7 +322,8 @@ mod tests {
|
|||
let secp = secp_inst.lock();
|
||||
let mut test_rng = StepRng::new(1_234_567_890_u64, 1);
|
||||
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
||||
output_tor_listener_config(test_dir, "127.0.0.1:3415", &[sec_key])?;
|
||||
let hm = HashMap::new();
|
||||
output_tor_listener_config(test_dir, "127.0.0.1:3415", &[sec_key], hm.clone(), hm)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -12,5 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod bridge;
|
||||
pub mod config;
|
||||
pub mod process;
|
||||
pub mod proxy;
|
||||
|
|
191
impls/src/tor/proxy.rs
Normal file
191
impls/src/tor/proxy.rs
Normal file
|
@ -0,0 +1,191 @@
|
|||
// 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, ErrorKind};
|
||||
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| ErrorKind::TorProxy(format!("Invalid host address: {}", host)))?;
|
||||
let port = if let Some(p) = str_port {
|
||||
let res = p
|
||||
.parse::<u16>()
|
||||
.map_err(|_e| ErrorKind::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(ErrorKind::TorProxy(msg).into());
|
||||
}
|
||||
}
|
||||
// Missing transport type
|
||||
_ => {
|
||||
let msg = format!(
|
||||
"Invalid proxy transport: {} - must be socks4/socks5/http(s)",
|
||||
transport
|
||||
);
|
||||
Err(ErrorKind::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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -88,6 +88,11 @@ subcommands:
|
|||
short: n
|
||||
long: no_tor
|
||||
takes_value: false
|
||||
- bridge:
|
||||
help: Enable bridge relay with TOR listener
|
||||
short: g
|
||||
long: bridge
|
||||
takes_value: true
|
||||
- owner_api:
|
||||
about: Runs the wallet's local web API
|
||||
args:
|
||||
|
@ -167,6 +172,11 @@ subcommands:
|
|||
short: u
|
||||
long: outfile
|
||||
takes_value: true
|
||||
- bridge:
|
||||
help: Enable tor bridge relay when sending via Slatepack workflow
|
||||
short: g
|
||||
long: bridge
|
||||
takes_value: true
|
||||
- unpack:
|
||||
about: Unpack and display an armored Slatepack Message, decrypting if possible
|
||||
args:
|
||||
|
@ -192,6 +202,11 @@ subcommands:
|
|||
short: u
|
||||
long: outfile
|
||||
takes_value: true
|
||||
- bridge:
|
||||
help: Enable tor bridge relay when receiving via Slatepack workflow
|
||||
short: g
|
||||
long: bridge
|
||||
takes_value: true
|
||||
- finalize:
|
||||
about: Processes a Slatepack Message to finalize a transfer.
|
||||
args:
|
||||
|
@ -275,6 +290,11 @@ subcommands:
|
|||
short: u
|
||||
long: outfile
|
||||
takes_value: true
|
||||
- bridge:
|
||||
help: Enable tor bridge relay when paying invoice.
|
||||
short: g
|
||||
long: bridge
|
||||
takes_value: true
|
||||
- outputs:
|
||||
about: Raw wallet output info (list of outputs)
|
||||
- txs:
|
||||
|
|
|
@ -394,6 +394,9 @@ pub fn parse_listen_args(
|
|||
if let Some(port) = args.value_of("port") {
|
||||
config.api_listen_port = port.parse().unwrap();
|
||||
}
|
||||
if let Some(bridge) = args.value_of("bridge") {
|
||||
tor_config.bridge.bridge_line = Some(bridge.into());
|
||||
}
|
||||
if args.is_present("no_tor") {
|
||||
tor_config.use_tor_listener = false;
|
||||
}
|
||||
|
@ -512,6 +515,11 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
|
|||
|
||||
let outfile = parse_optional(args, "outfile")?;
|
||||
|
||||
let bridge = match args.value_of("bridge") {
|
||||
Some(b) => Some(b.to_string()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(command::SendArgs {
|
||||
amount: amount,
|
||||
minimum_confirmations: min_c,
|
||||
|
@ -527,6 +535,7 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
|
|||
target_slate_version: target_slate_version,
|
||||
outfile,
|
||||
skip_tor: args.is_present("manual"),
|
||||
bridge: bridge,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -552,11 +561,14 @@ pub fn parse_receive_args(args: &ArgMatches) -> Result<command::ReceiveArgs, Par
|
|||
|
||||
let outfile = parse_optional(args, "outfile")?;
|
||||
|
||||
let bridge = parse_optional(args, "bridge")?;
|
||||
|
||||
Ok(command::ReceiveArgs {
|
||||
input_file,
|
||||
input_slatepack_message,
|
||||
skip_tor: args.is_present("manual"),
|
||||
outfile,
|
||||
bridge,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -582,11 +594,14 @@ pub fn parse_unpack_args(args: &ArgMatches) -> Result<command::ReceiveArgs, Pars
|
|||
|
||||
let outfile = parse_optional(args, "outfile")?;
|
||||
|
||||
let bridge = parse_optional(args, "bridge")?;
|
||||
|
||||
Ok(command::ReceiveArgs {
|
||||
input_file,
|
||||
input_slatepack_message,
|
||||
skip_tor: args.is_present("manual"),
|
||||
outfile,
|
||||
bridge,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -741,6 +756,8 @@ pub fn parse_process_invoice_args(
|
|||
|
||||
let outfile = parse_optional(args, "outfile")?;
|
||||
|
||||
let bridge = parse_optional(args, "bridge")?;
|
||||
|
||||
Ok(command::ProcessInvoiceArgs {
|
||||
minimum_confirmations: min_c,
|
||||
selection_strategy: selection_strategy.to_owned(),
|
||||
|
@ -751,6 +768,7 @@ pub fn parse_process_invoice_args(
|
|||
ttl_blocks,
|
||||
skip_tor: args.is_present("manual"),
|
||||
outfile,
|
||||
bridge,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ pub fn config_command_wallet(
|
|||
}
|
||||
default_config.update_paths(¤t_dir);
|
||||
default_config
|
||||
.write_to_file(config_file_name.to_str().unwrap())
|
||||
.write_to_file(config_file_name.to_str().unwrap(), false, None, None)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("Error creating config file: {}", e);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue