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"
|
name = "grin_wallet_impls"
|
||||||
version = "5.1.0-alpha.1"
|
version = "5.1.0-alpha.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64 0.12.3",
|
||||||
"blake2-rfc",
|
"blake2-rfc",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -1570,6 +1571,7 @@ dependencies = [
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"timer",
|
"timer",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
"x25519-dalek 0.6.0",
|
"x25519-dalek 0.6.0",
|
||||||
]
|
]
|
||||||
|
|
|
@ -2451,6 +2451,8 @@ pub fn try_slatepack_sync_workflow(
|
||||||
&tor_addr.to_http_str(),
|
&tor_addr.to_http_str(),
|
||||||
&tor_config.as_ref().unwrap().socks_proxy_addr,
|
&tor_config.as_ref().unwrap().socks_proxy_addr,
|
||||||
&tor_config.as_ref().unwrap().send_config_dir,
|
&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),
|
Ok(s) => Some(s),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
|
@ -19,6 +19,14 @@ use std::collections::HashMap;
|
||||||
fn comments() -> HashMap<String, String> {
|
fn comments() -> HashMap<String, String> {
|
||||||
let mut retval = HashMap::new();
|
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(
|
retval.insert(
|
||||||
"[wallet]".to_string(),
|
"[wallet]".to_string(),
|
||||||
"
|
"
|
||||||
|
@ -124,6 +132,22 @@ fn comments() -> HashMap<String, String> {
|
||||||
retval.insert(
|
retval.insert(
|
||||||
"[logging]".to_string(),
|
"[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 ###
|
### LOGGING CONFIGURATION ###
|
||||||
#########################################
|
#########################################
|
||||||
|
@ -214,14 +238,6 @@ fn comments() -> HashMap<String, String> {
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
retval.insert(
|
|
||||||
"socks_proxy_addr".to_string(),
|
|
||||||
"
|
|
||||||
# TOR (SOCKS) proxy server address
|
|
||||||
"
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
retval.insert(
|
retval.insert(
|
||||||
"send_config_dir".to_string(),
|
"send_config_dir".to_string(),
|
||||||
"
|
"
|
||||||
|
@ -230,6 +246,37 @@ fn comments() -> HashMap<String, String> {
|
||||||
.to_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
|
retval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,3 +308,92 @@ pub fn insert_comments(orig: String) -> String {
|
||||||
}
|
}
|
||||||
ret_val
|
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::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::io::Read;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use toml;
|
use toml;
|
||||||
|
|
||||||
use crate::comments::insert_comments;
|
use crate::comments::{insert_comments, migrate_comments};
|
||||||
use crate::core::global;
|
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::types::{TorConfig, WalletConfig};
|
||||||
use crate::util::logger::LoggingConfig;
|
use crate::util::logger::LoggingConfig;
|
||||||
|
|
||||||
|
@ -187,6 +188,7 @@ pub fn initial_setup_wallet(
|
||||||
impl Default for GlobalWalletConfigMembers {
|
impl Default for GlobalWalletConfigMembers {
|
||||||
fn default() -> GlobalWalletConfigMembers {
|
fn default() -> GlobalWalletConfigMembers {
|
||||||
GlobalWalletConfigMembers {
|
GlobalWalletConfigMembers {
|
||||||
|
config_file_version: Some(2),
|
||||||
logging: Some(LoggingConfig::default()),
|
logging: Some(LoggingConfig::default()),
|
||||||
tor: Some(TorConfig::default()),
|
tor: Some(TorConfig::default()),
|
||||||
wallet: WalletConfig::default(),
|
wallet: WalletConfig::default(),
|
||||||
|
@ -245,10 +247,13 @@ impl GlobalWalletConfig {
|
||||||
|
|
||||||
/// Read config
|
/// Read config
|
||||||
fn read_config(mut self) -> Result<GlobalWalletConfig, ConfigError> {
|
fn read_config(mut self) -> Result<GlobalWalletConfig, ConfigError> {
|
||||||
let mut file = File::open(self.config_file_path.as_mut().unwrap())?;
|
let config_file_path = self.config_file_path.as_mut().unwrap();
|
||||||
let mut contents = String::new();
|
let contents = fs::read_to_string(config_file_path.clone())?;
|
||||||
file.read_to_string(&mut contents)?;
|
let migrated = GlobalWalletConfig::migrate_config_file_version_none_to_2(
|
||||||
let fixed = GlobalWalletConfig::fix_warning_level(contents);
|
contents,
|
||||||
|
config_file_path.to_owned(),
|
||||||
|
)?;
|
||||||
|
let fixed = GlobalWalletConfig::fix_warning_level(migrated);
|
||||||
let decoded: Result<GlobalWalletConfigMembers, toml::de::Error> = toml::from_str(&fixed);
|
let decoded: Result<GlobalWalletConfigMembers, toml::de::Error> = toml::from_str(&fixed);
|
||||||
match decoded {
|
match decoded {
|
||||||
Ok(gc) => {
|
Ok(gc) => {
|
||||||
|
@ -306,14 +311,60 @@ impl GlobalWalletConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write configuration to a file
|
/// 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 conf_out = self.ser_config()?;
|
||||||
let fixed_config = GlobalWalletConfig::fix_log_level(conf_out);
|
let commented_config = if migration {
|
||||||
let commented_config = insert_comments(fixed_config);
|
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)?;
|
let mut file = File::create(name)?;
|
||||||
file.write_all(commented_config.as_bytes())?;
|
file.write_all(commented_config.as_bytes())?;
|
||||||
Ok(())
|
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`
|
// For forwards compatibility old config needs `Warning` log level changed to standard log::Level `WARN`
|
||||||
fn fix_warning_level(conf: String) -> String {
|
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
|
/// Tor configuration
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct TorConfig {
|
pub struct TorConfig {
|
||||||
|
@ -164,6 +173,12 @@ pub struct TorConfig {
|
||||||
pub socks_proxy_addr: String,
|
pub socks_proxy_addr: String,
|
||||||
/// Send configuration directory
|
/// Send configuration directory
|
||||||
pub send_config_dir: String,
|
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 {
|
impl Default for TorConfig {
|
||||||
|
@ -173,15 +188,66 @@ impl Default for TorConfig {
|
||||||
use_tor_listener: true,
|
use_tor_listener: true,
|
||||||
socks_proxy_addr: "127.0.0.1:59050".to_owned(),
|
socks_proxy_addr: "127.0.0.1:59050".to_owned(),
|
||||||
send_config_dir: ".".into(),
|
send_config_dir: ".".into(),
|
||||||
|
bridge: TorBridgeConfig::default(),
|
||||||
|
proxy: TorProxyConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<io::Error> for ConfigError {
|
|
||||||
fn from(error: io::Error) -> ConfigError {
|
/// Tor Bridge Config
|
||||||
ConfigError::FileIOError(
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
String::from(""),
|
pub struct TorBridgeConfig {
|
||||||
format!("Error loading config file: {}", error),
|
/// 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
|
/// Wallet internal members
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct GlobalWalletConfigMembers {
|
pub struct GlobalWalletConfigMembers {
|
||||||
|
/// Config file version (None == version 1)
|
||||||
|
#[serde(default)]
|
||||||
|
pub config_file_version: Option<u32>,
|
||||||
/// Wallet configuration
|
/// Wallet configuration
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub wallet: WalletConfig,
|
pub wallet: WalletConfig,
|
||||||
|
|
|
@ -334,6 +334,7 @@ pub struct SendArgs {
|
||||||
pub ttl_blocks: Option<u64>,
|
pub ttl_blocks: Option<u64>,
|
||||||
pub skip_tor: bool,
|
pub skip_tor: bool,
|
||||||
pub outfile: Option<String>,
|
pub outfile: Option<String>,
|
||||||
|
pub bridge: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send<L, C, K>(
|
pub fn send<L, C, K>(
|
||||||
|
@ -412,6 +413,9 @@ where
|
||||||
|
|
||||||
let tor_config = match tor_config {
|
let tor_config = match tor_config {
|
||||||
Some(mut c) => {
|
Some(mut c) => {
|
||||||
|
if let Some(b) = args.bridge.clone() {
|
||||||
|
c.bridge.bridge_line = Some(b);
|
||||||
|
}
|
||||||
c.skip_send_attempt = Some(args.skip_tor);
|
c.skip_send_attempt = Some(args.skip_tor);
|
||||||
Some(c)
|
Some(c)
|
||||||
}
|
}
|
||||||
|
@ -605,6 +609,7 @@ pub struct ReceiveArgs {
|
||||||
pub input_slatepack_message: Option<String>,
|
pub input_slatepack_message: Option<String>,
|
||||||
pub skip_tor: bool,
|
pub skip_tor: bool,
|
||||||
pub outfile: Option<String>,
|
pub outfile: Option<String>,
|
||||||
|
pub bridge: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn receive<L, C, K>(
|
pub fn receive<L, C, K>(
|
||||||
|
@ -634,6 +639,9 @@ where
|
||||||
|
|
||||||
let tor_config = match tor_config {
|
let tor_config = match tor_config {
|
||||||
Some(mut c) => {
|
Some(mut c) => {
|
||||||
|
if let Some(b) = args.bridge {
|
||||||
|
c.bridge.bridge_line = Some(b);
|
||||||
|
}
|
||||||
c.skip_send_attempt = Some(args.skip_tor);
|
c.skip_send_attempt = Some(args.skip_tor);
|
||||||
Some(c)
|
Some(c)
|
||||||
}
|
}
|
||||||
|
@ -889,6 +897,7 @@ pub struct ProcessInvoiceArgs {
|
||||||
pub ttl_blocks: Option<u64>,
|
pub ttl_blocks: Option<u64>,
|
||||||
pub skip_tor: bool,
|
pub skip_tor: bool,
|
||||||
pub outfile: Option<String>,
|
pub outfile: Option<String>,
|
||||||
|
pub bridge: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process invoice
|
/// Process invoice
|
||||||
|
@ -965,6 +974,9 @@ where
|
||||||
|
|
||||||
let tor_config = match tor_config {
|
let tor_config = match tor_config {
|
||||||
Some(mut c) => {
|
Some(mut c) => {
|
||||||
|
if let Some(b) = args.bridge {
|
||||||
|
c.bridge.bridge_line = Some(b);
|
||||||
|
}
|
||||||
c.skip_send_attempt = Some(args.skip_tor);
|
c.skip_send_attempt = Some(args.skip_tor);
|
||||||
Some(c)
|
Some(c)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ use crate::util::secp::key::SecretKey;
|
||||||
use crate::util::{from_hex, static_secp_instance, to_base64, Mutex};
|
use crate::util::{from_hex, static_secp_instance, to_base64, Mutex};
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
use grin_wallet_api::JsonId;
|
use grin_wallet_api::JsonId;
|
||||||
|
use grin_wallet_config::types::{TorBridgeConfig, TorProxyConfig};
|
||||||
use grin_wallet_util::OnionV3Address;
|
use grin_wallet_util::OnionV3Address;
|
||||||
use hyper::body;
|
use hyper::body;
|
||||||
use hyper::header::HeaderValue;
|
use hyper::header::HeaderValue;
|
||||||
|
@ -38,6 +39,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use crate::impls::tor::config as tor_config;
|
use crate::impls::tor::config as tor_config;
|
||||||
use crate::impls::tor::process as tor_process;
|
use crate::impls::tor::process as tor_process;
|
||||||
|
use crate::impls::tor::{bridge as tor_bridge, proxy as tor_proxy};
|
||||||
|
|
||||||
use crate::apiwallet::{
|
use crate::apiwallet::{
|
||||||
EncryptedRequest, EncryptedResponse, EncryptionErrorResponse, Foreign,
|
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>>>,
|
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
|
||||||
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
|
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
|
||||||
addr: &str,
|
addr: &str,
|
||||||
|
bridge: TorBridgeConfig,
|
||||||
|
tor_proxy: TorProxyConfig,
|
||||||
) -> Result<(tor_process::TorProcess, SlatepackAddress), Error>
|
) -> Result<(tor_process::TorProcess, SlatepackAddress), Error>
|
||||||
where
|
where
|
||||||
L: WalletLCProvider<'static, C, K> + 'static,
|
L: WalletLCProvider<'static, C, K> + 'static,
|
||||||
|
@ -103,17 +107,44 @@ where
|
||||||
let onion_address = OnionV3Address::from_private(&sec_key.0)
|
let onion_address = OnionV3Address::from_private(&sec_key.0)
|
||||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||||
let sp_address = SlatepackAddress::try_from(onion_address.clone())?;
|
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!(
|
warn!(
|
||||||
"Starting Tor Hidden Service for API listener at address {}, binding to {}",
|
"Starting Tor Hidden Service for API listener at address {}, binding to {}",
|
||||||
onion_address, addr
|
onion_address, addr
|
||||||
);
|
);
|
||||||
tor_config::output_tor_listener_config(&tor_dir, addr, &vec![sec_key])
|
tor_config::output_tor_listener_config(
|
||||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
&tor_dir,
|
||||||
|
addr,
|
||||||
|
&vec![sec_key],
|
||||||
|
hm_tor_bridge,
|
||||||
|
hm_tor_poxy,
|
||||||
|
)
|
||||||
|
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||||
// Start TOR process
|
// Start TOR process
|
||||||
process
|
process
|
||||||
.torrc_path(&format!("{}/torrc", tor_dir))
|
.torrc_path(&format!("{}/torrc", tor_dir))
|
||||||
.working_dir(&tor_dir)
|
.working_dir(&tor_dir)
|
||||||
.timeout(20)
|
.timeout(tor_timeout)
|
||||||
.completion_percent(100)
|
.completion_percent(100)
|
||||||
.launch()
|
.launch()
|
||||||
.map_err(|e| ErrorKind::TorProcess(format!("{:?}", e).into()))?;
|
.map_err(|e| ErrorKind::TorProcess(format!("{:?}", e).into()))?;
|
||||||
|
@ -266,17 +297,31 @@ where
|
||||||
let lc = w_lock.lc_provider()?;
|
let lc = w_lock.lc_provider()?;
|
||||||
let _ = lc.wallet_inst()?;
|
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
|
// need to keep in scope while the main listener is running
|
||||||
let (_tor_process, address) = match use_tor {
|
let (_tor_process, address) = match use_tor {
|
||||||
true => match init_tor_listener(wallet.clone(), keychain_mask.clone(), addr) {
|
true => {
|
||||||
Ok((tp, addr)) => (Some(tp), Some(addr)),
|
match init_tor_listener(
|
||||||
Err(e) => {
|
wallet.clone(),
|
||||||
warn!("Unable to start TOR listener; Check that TOR executable is installed and on your path");
|
keychain_mask.clone(),
|
||||||
warn!("Tor Error: {}", e);
|
addr,
|
||||||
warn!("Listener will be available via HTTP only");
|
tor_bridge,
|
||||||
(None, None)
|
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),
|
false => (None, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ lazy_static = "1"
|
||||||
tokio = { version = "0.2", features = ["full"] }
|
tokio = { version = "0.2", features = ["full"] }
|
||||||
reqwest = { version = "0.10", features = ["rustls-tls", "socks"] }
|
reqwest = { version = "0.10", features = ["rustls-tls", "socks"] }
|
||||||
|
|
||||||
#Socks/Tor
|
#Socks/Tor/Bridge/Proxy
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
ed25519-dalek = "1.0.0-pre.4"
|
ed25519-dalek = "1.0.0-pre.4"
|
||||||
x25519-dalek = "0.6"
|
x25519-dalek = "0.6"
|
||||||
|
@ -34,6 +34,8 @@ data-encoding = "2"
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
timer = "0.2"
|
timer = "0.2"
|
||||||
sysinfo = "0.14"
|
sysinfo = "0.14"
|
||||||
|
base64 = "0.12.0"
|
||||||
|
url = "2.1"
|
||||||
|
|
||||||
grin_wallet_util = { path = "../util", version = "5.1.0-alpha.1" }
|
grin_wallet_util = { path = "../util", version = "5.1.0-alpha.1" }
|
||||||
grin_wallet_config = { path = "../config", 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::client_utils::{Client, ClientError, ClientErrorKind};
|
||||||
use crate::libwallet::slate_versions::{SlateVersion, VersionedSlate};
|
use crate::libwallet::slate_versions::{SlateVersion, VersionedSlate};
|
||||||
use crate::libwallet::{Error, ErrorKind, Slate};
|
use crate::libwallet::{Error, ErrorKind, Slate};
|
||||||
|
use crate::tor::bridge::TorBridge;
|
||||||
|
use crate::tor::proxy::TorProxy;
|
||||||
use crate::SlateSender;
|
use crate::SlateSender;
|
||||||
|
use grin_wallet_config::types::{TorBridgeConfig, TorProxyConfig};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::path::MAIN_SEPARATOR;
|
use std::path::MAIN_SEPARATOR;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -35,6 +40,8 @@ pub struct HttpSlateSender {
|
||||||
socks_proxy_addr: Option<SocketAddr>,
|
socks_proxy_addr: Option<SocketAddr>,
|
||||||
tor_config_dir: String,
|
tor_config_dir: String,
|
||||||
process: Option<Arc<tor_process::TorProcess>>,
|
process: Option<Arc<tor_process::TorProcess>>,
|
||||||
|
bridge: TorBridgeConfig,
|
||||||
|
proxy: TorProxyConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpSlateSender {
|
impl HttpSlateSender {
|
||||||
|
@ -49,6 +56,8 @@ impl HttpSlateSender {
|
||||||
socks_proxy_addr: None,
|
socks_proxy_addr: None,
|
||||||
tor_config_dir: String::from(""),
|
tor_config_dir: String::from(""),
|
||||||
process: None,
|
process: None,
|
||||||
|
bridge: TorBridgeConfig::default(),
|
||||||
|
proxy: TorProxyConfig::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,12 +67,16 @@ impl HttpSlateSender {
|
||||||
base_url: &str,
|
base_url: &str,
|
||||||
proxy_addr: &str,
|
proxy_addr: &str,
|
||||||
tor_config_dir: &str,
|
tor_config_dir: &str,
|
||||||
|
tor_bridge: TorBridgeConfig,
|
||||||
|
tor_proxy: TorProxyConfig,
|
||||||
) -> Result<HttpSlateSender, SchemeNotHttp> {
|
) -> Result<HttpSlateSender, SchemeNotHttp> {
|
||||||
let mut ret = Self::new(base_url)?;
|
let mut ret = Self::new(base_url)?;
|
||||||
ret.use_socks = true;
|
ret.use_socks = true;
|
||||||
//TODO: Unwrap
|
//TODO: Unwrap
|
||||||
ret.socks_proxy_addr = Some(SocketAddr::V4(proxy_addr.parse().unwrap()));
|
ret.socks_proxy_addr = Some(SocketAddr::V4(proxy_addr.parse().unwrap()));
|
||||||
ret.tor_config_dir = tor_config_dir.into();
|
ret.tor_config_dir = tor_config_dir.into();
|
||||||
|
ret.bridge = tor_bridge;
|
||||||
|
ret.proxy = tor_proxy;
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,9 +93,30 @@ impl HttpSlateSender {
|
||||||
"Starting TOR Process for send at {:?}",
|
"Starting TOR Process for send at {:?}",
|
||||||
self.socks_proxy_addr
|
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_config::output_tor_sender_config(
|
||||||
&tor_dir,
|
&tor_dir,
|
||||||
&self.socks_proxy_addr.unwrap().to_string(),
|
&self.socks_proxy_addr.unwrap().to_string(),
|
||||||
|
hm_tor_bridge,
|
||||||
|
hm_tor_proxy,
|
||||||
)
|
)
|
||||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e)))?;
|
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e)))?;
|
||||||
// Start TOR process
|
// Start TOR process
|
||||||
|
|
|
@ -47,6 +47,14 @@ pub enum ErrorKind {
|
||||||
#[fail(display = "Onion V3 Address Error")]
|
#[fail(display = "Onion V3 Address Error")]
|
||||||
OnionV3Address(OnionV3AddressError),
|
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
|
/// Error when formatting json
|
||||||
#[fail(display = "IO error")]
|
#[fail(display = "IO error")]
|
||||||
IO,
|
IO,
|
||||||
|
@ -83,6 +91,14 @@ pub enum ErrorKind {
|
||||||
#[fail(display = "{}", _0)]
|
#[fail(display = "{}", _0)]
|
||||||
ArgumentError(String),
|
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
|
/// Generating ED25519 Public Key
|
||||||
#[fail(display = "Error generating ed25519 secret key: {}", _0)]
|
#[fail(display = "Error generating ed25519 secret key: {}", _0)]
|
||||||
ED25519Key(String),
|
ED25519Key(String),
|
||||||
|
|
|
@ -79,6 +79,10 @@ where
|
||||||
tor_config: Option<TorConfig>,
|
tor_config: Option<TorConfig>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut default_config = GlobalWalletConfig::for_chain(&chain_type);
|
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 {
|
let logging = match logging_config {
|
||||||
Some(l) => Some(l),
|
Some(l) => Some(l),
|
||||||
None => match default_config.members.as_ref() {
|
None => match default_config.members.as_ref() {
|
||||||
|
@ -102,6 +106,7 @@ where
|
||||||
};
|
};
|
||||||
default_config = GlobalWalletConfig {
|
default_config = GlobalWalletConfig {
|
||||||
members: Some(GlobalWalletConfigMembers {
|
members: Some(GlobalWalletConfigMembers {
|
||||||
|
config_file_version,
|
||||||
wallet,
|
wallet,
|
||||||
tor,
|
tor,
|
||||||
logging,
|
logging,
|
||||||
|
@ -139,7 +144,8 @@ where
|
||||||
abs_path.push(self.data_dir.clone());
|
abs_path.push(self.data_dir.clone());
|
||||||
|
|
||||||
default_config.update_paths(&abs_path);
|
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 {
|
if let Err(e) = res {
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"Error creating config file as ({}): {}",
|
"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::ExpandedSecretKey;
|
||||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, MAIN_SEPARATOR};
|
use std::path::{Path, MAIN_SEPARATOR};
|
||||||
|
use std::string::String;
|
||||||
|
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
|
|
||||||
|
@ -171,6 +172,8 @@ pub fn output_torrc(
|
||||||
wallet_listener_addr: &str,
|
wallet_listener_addr: &str,
|
||||||
socks_port: &str,
|
socks_port: &str,
|
||||||
service_dirs: &[String],
|
service_dirs: &[String],
|
||||||
|
hm_tor_bridge: HashMap<String, String>,
|
||||||
|
hm_tor_proxy: HashMap<String, String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let torrc_file_path = format!("{}{}{}", tor_config_directory, MAIN_SEPARATOR, TORRC_FILE);
|
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));
|
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)?;
|
props.write_to_file(&torrc_file_path)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -196,6 +212,8 @@ pub fn output_tor_listener_config(
|
||||||
tor_config_directory: &str,
|
tor_config_directory: &str,
|
||||||
wallet_listener_addr: &str,
|
wallet_listener_addr: &str,
|
||||||
listener_keys: &[SecretKey],
|
listener_keys: &[SecretKey],
|
||||||
|
hm_tor_bridge: HashMap<String, String>,
|
||||||
|
hm_tor_proxy: HashMap<String, String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let tor_data_dir = format!("{}{}{}", tor_config_directory, MAIN_SEPARATOR, TOR_DATA_DIR);
|
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,
|
wallet_listener_addr,
|
||||||
"0",
|
"0",
|
||||||
&service_dirs,
|
&service_dirs,
|
||||||
|
hm_tor_bridge,
|
||||||
|
hm_tor_proxy,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -224,11 +244,20 @@ pub fn output_tor_listener_config(
|
||||||
pub fn output_tor_sender_config(
|
pub fn output_tor_sender_config(
|
||||||
tor_config_dir: &str,
|
tor_config_dir: &str,
|
||||||
socks_listener_addr: &str,
|
socks_listener_addr: &str,
|
||||||
|
hm_tor_bridge: HashMap<String, String>,
|
||||||
|
hm_tor_proxy: HashMap<String, String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// create data directory if it doesn't exist
|
// create data directory if it doesn't exist
|
||||||
fs::create_dir_all(&tor_config_dir).context(ErrorKind::IO)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -293,7 +322,8 @@ mod tests {
|
||||||
let secp = secp_inst.lock();
|
let secp = secp_inst.lock();
|
||||||
let mut test_rng = StepRng::new(1_234_567_890_u64, 1);
|
let mut test_rng = StepRng::new(1_234_567_890_u64, 1);
|
||||||
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
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);
|
clean_output_dir(test_dir);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,5 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
pub mod bridge;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod process;
|
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
|
short: n
|
||||||
long: no_tor
|
long: no_tor
|
||||||
takes_value: false
|
takes_value: false
|
||||||
|
- bridge:
|
||||||
|
help: Enable bridge relay with TOR listener
|
||||||
|
short: g
|
||||||
|
long: bridge
|
||||||
|
takes_value: true
|
||||||
- owner_api:
|
- owner_api:
|
||||||
about: Runs the wallet's local web API
|
about: Runs the wallet's local web API
|
||||||
args:
|
args:
|
||||||
|
@ -167,6 +172,11 @@ subcommands:
|
||||||
short: u
|
short: u
|
||||||
long: outfile
|
long: outfile
|
||||||
takes_value: true
|
takes_value: true
|
||||||
|
- bridge:
|
||||||
|
help: Enable tor bridge relay when sending via Slatepack workflow
|
||||||
|
short: g
|
||||||
|
long: bridge
|
||||||
|
takes_value: true
|
||||||
- unpack:
|
- unpack:
|
||||||
about: Unpack and display an armored Slatepack Message, decrypting if possible
|
about: Unpack and display an armored Slatepack Message, decrypting if possible
|
||||||
args:
|
args:
|
||||||
|
@ -192,6 +202,11 @@ subcommands:
|
||||||
short: u
|
short: u
|
||||||
long: outfile
|
long: outfile
|
||||||
takes_value: true
|
takes_value: true
|
||||||
|
- bridge:
|
||||||
|
help: Enable tor bridge relay when receiving via Slatepack workflow
|
||||||
|
short: g
|
||||||
|
long: bridge
|
||||||
|
takes_value: true
|
||||||
- finalize:
|
- finalize:
|
||||||
about: Processes a Slatepack Message to finalize a transfer.
|
about: Processes a Slatepack Message to finalize a transfer.
|
||||||
args:
|
args:
|
||||||
|
@ -275,6 +290,11 @@ subcommands:
|
||||||
short: u
|
short: u
|
||||||
long: outfile
|
long: outfile
|
||||||
takes_value: true
|
takes_value: true
|
||||||
|
- bridge:
|
||||||
|
help: Enable tor bridge relay when paying invoice.
|
||||||
|
short: g
|
||||||
|
long: bridge
|
||||||
|
takes_value: true
|
||||||
- outputs:
|
- outputs:
|
||||||
about: Raw wallet output info (list of outputs)
|
about: Raw wallet output info (list of outputs)
|
||||||
- txs:
|
- txs:
|
||||||
|
|
|
@ -394,6 +394,9 @@ pub fn parse_listen_args(
|
||||||
if let Some(port) = args.value_of("port") {
|
if let Some(port) = args.value_of("port") {
|
||||||
config.api_listen_port = port.parse().unwrap();
|
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") {
|
if args.is_present("no_tor") {
|
||||||
tor_config.use_tor_listener = false;
|
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 outfile = parse_optional(args, "outfile")?;
|
||||||
|
|
||||||
|
let bridge = match args.value_of("bridge") {
|
||||||
|
Some(b) => Some(b.to_string()),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(command::SendArgs {
|
Ok(command::SendArgs {
|
||||||
amount: amount,
|
amount: amount,
|
||||||
minimum_confirmations: min_c,
|
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,
|
target_slate_version: target_slate_version,
|
||||||
outfile,
|
outfile,
|
||||||
skip_tor: args.is_present("manual"),
|
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 outfile = parse_optional(args, "outfile")?;
|
||||||
|
|
||||||
|
let bridge = parse_optional(args, "bridge")?;
|
||||||
|
|
||||||
Ok(command::ReceiveArgs {
|
Ok(command::ReceiveArgs {
|
||||||
input_file,
|
input_file,
|
||||||
input_slatepack_message,
|
input_slatepack_message,
|
||||||
skip_tor: args.is_present("manual"),
|
skip_tor: args.is_present("manual"),
|
||||||
outfile,
|
outfile,
|
||||||
|
bridge,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,11 +594,14 @@ pub fn parse_unpack_args(args: &ArgMatches) -> Result<command::ReceiveArgs, Pars
|
||||||
|
|
||||||
let outfile = parse_optional(args, "outfile")?;
|
let outfile = parse_optional(args, "outfile")?;
|
||||||
|
|
||||||
|
let bridge = parse_optional(args, "bridge")?;
|
||||||
|
|
||||||
Ok(command::ReceiveArgs {
|
Ok(command::ReceiveArgs {
|
||||||
input_file,
|
input_file,
|
||||||
input_slatepack_message,
|
input_slatepack_message,
|
||||||
skip_tor: args.is_present("manual"),
|
skip_tor: args.is_present("manual"),
|
||||||
outfile,
|
outfile,
|
||||||
|
bridge,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -741,6 +756,8 @@ pub fn parse_process_invoice_args(
|
||||||
|
|
||||||
let outfile = parse_optional(args, "outfile")?;
|
let outfile = parse_optional(args, "outfile")?;
|
||||||
|
|
||||||
|
let bridge = parse_optional(args, "bridge")?;
|
||||||
|
|
||||||
Ok(command::ProcessInvoiceArgs {
|
Ok(command::ProcessInvoiceArgs {
|
||||||
minimum_confirmations: min_c,
|
minimum_confirmations: min_c,
|
||||||
selection_strategy: selection_strategy.to_owned(),
|
selection_strategy: selection_strategy.to_owned(),
|
||||||
|
@ -751,6 +768,7 @@ pub fn parse_process_invoice_args(
|
||||||
ttl_blocks,
|
ttl_blocks,
|
||||||
skip_tor: args.is_present("manual"),
|
skip_tor: args.is_present("manual"),
|
||||||
outfile,
|
outfile,
|
||||||
|
bridge,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -167,7 +167,7 @@ pub fn config_command_wallet(
|
||||||
}
|
}
|
||||||
default_config.update_paths(¤t_dir);
|
default_config.update_paths(¤t_dir);
|
||||||
default_config
|
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| {
|
.unwrap_or_else(|e| {
|
||||||
panic!("Error creating config file: {}", e);
|
panic!("Error creating config file: {}", e);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue