use crate::Error;
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: &'a str,
flags: Vec<&'a str>,
bool_flags: Vec<&'a str>,
is_bool_flag: bool,
client: bool,
}
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,
}
}
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
}
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 {
let split_index = self.is_flag();
let (l, r) = self.line.split_at(split_index);
(l, r)
} else {
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))
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Transport {
pub transport: Option<String>,
pub server: Option<String>,
pub fingerprint: Option<String>,
pub cert: Option<String>,
pub iatmode: Option<String>,
pub url: Option<String>,
pub front: Option<String>,
pub utls: Option<String>,
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 {
fn parse_socketaddr_arg(arg: Option<&&str>) -> Result<String, Error> {
match arg {
Some(addr) => {
let address = addr.parse::<SocketAddr>().map_err(|_e| {
Error::TorBridge(format!("Invalid bridge server address: {}", addr))
})?;
Ok(address.to_string())
}
None => {
let msg = format!("Missing bridge server address");
Err(Error::TorBridge(msg))
}
}
}
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(Error::TorBridge(msg));
}
Ok(Some(fingerprint))
}
None => Ok(None),
}
}
pub fn parse_cert_arg(arg: &str) -> Result<String, Error> {
let cert_vec = base64::decode(arg).map_err(|_e| {
Error::TorBridge(format!(
"Invalid certificate, error decoding bridge certificate: {}",
arg
))
})?;
if cert_vec.len() != 52 {
let msg = format!("Invalid certificate: {}", arg);
return Err(Error::TorBridge(msg).into());
}
Ok(arg.to_string())
}
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(Error::TorBridge(msg));
}
Ok(iatmode.to_string())
}
fn parse_hpkp_arg(arg: &str) -> Result<String, Error> {
let max = arg.parse::<bool>().map_err(|_e| {
Error::TorBridge(
format!("Invalid -max value: {}, must be \"true\" or \"false\"", arg).into(),
)
})?;
Ok(max.to_string())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct PluginClient {
pub path: Option<String>,
pub option: Option<String>,
}
impl Default for PluginClient {
fn default() -> PluginClient {
PluginClient {
path: None,
option: None,
}
}
}
impl PluginClient {
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()
}
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(Error::TorBridge(msg))
}
}
}
fn parse_url_arg(arg: &str) -> Result<String, Error> {
let url = arg
.parse::<Url>()
.map_err(|_e| Error::TorBridge(format!("Invalid -url value: {}", arg)))?;
Ok(url.to_string())
}
fn parse_front_arg(arg: &str) -> Result<String, Error> {
let front = Host::parse(arg)
.map_err(|_e| Error::TorBridge(format!("Invalid -front hostname value: {}", arg)))?;
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(Error::TorBridge(msg))
}
}
}
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| Error::TorBridge(format!("{}", e)))?;
} else {
let msg = format!(
"Invalid ICE address: {}. Must be a stun or turn address",
addr
);
return Err(Error::TorBridge(msg).into());
}
}
Ok(ice_addr.to_string())
}
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(Error::TorBridge(msg))
}
}
}
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(Error::TorBridge(msg))
}
}
}
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(Error::TorBridge(msg));
}
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)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct TorBridge {
pub bridge: Transport,
pub client: PluginClient,
}
impl Default for TorBridge {
fn default() -> TorBridge {
TorBridge {
bridge: Transport::default(),
client: PluginClient::default(),
}
}
}
impl TorBridge {
pub fn get_flags(s: &str) -> HashMap<&str, &str> {
FlagParser::new(s, vec![], vec![], false).collect()
}
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(Error::TorBridge(msg))
}
}
}
}
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(Error::TorBridge(msg));
}
};
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(Error::TorBridge(msg));
}
};
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(Error::TorBridge(msg))
}
}
}
}