diff options
Diffstat (limited to 'mullvad-cli/src')
| -rw-r--r-- | mullvad-cli/src/cmds/bridge.rs | 354 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/mod.rs | 4 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/relay.rs | 82 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/tunnel.rs | 240 | ||||
| -rw-r--r-- | mullvad-cli/src/location.rs | 69 | ||||
| -rw-r--r-- | mullvad-cli/src/main.rs | 1 |
6 files changed, 435 insertions, 315 deletions
diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs new file mode 100644 index 0000000000..3c686154eb --- /dev/null +++ b/mullvad-cli/src/cmds/bridge.rs @@ -0,0 +1,354 @@ +use crate::{location, new_rpc_client, Command, Result}; +use clap::value_t; + +use mullvad_types::relay_constraints::{BridgeConstraints, BridgeSettings, BridgeState}; +use talpid_types::net::openvpn::{self, SHADOWSOCKS_CIPHERS}; + +use std::net::{IpAddr, SocketAddr}; + +pub struct Bridge; + +impl Command for Bridge { + fn name(&self) -> &'static str { + "bridge" + } + + fn clap_subcommand(&self) -> clap::App<'static, 'static> { + clap::SubCommand::with_name(self.name()) + .about("Manage use of bridges") + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand(create_bridge_set_subcommand()) + .subcommand( + clap::SubCommand::with_name("get").about("Get current bridge settings and state"), + ) + .subcommand(clap::SubCommand::with_name("list").about("List brigde relays")) + } + + fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + match matches.subcommand() { + ("set", Some(set_matches)) => Self::handle_set(set_matches), + ("get", _) => Self::handle_get(), + ("list", _) => Self::list_bridge_relays(), + _ => unreachable!("unhandled command"), + } + } +} + +fn create_bridge_set_subcommand() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("set") + .about("Set bridge state and settings") + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand(create_set_state_subcommand()) + .subcommand(create_set_custom_settings_subcommand()) + .subcommand(location::get_subcommand().about( + "Set country or city to select bridge relays from. Use the 'list' \ + command to show available alternatives.", + )) +} + + +fn create_set_custom_settings_subcommand() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("custom") + .about("Configure a SOCKS5 proxy") + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand( + clap::SubCommand::with_name("local") + .about("Registers a local SOCKS5 proxy") + .arg( + clap::Arg::with_name("local-port") + .help("Specifies the port the local proxy server is listening on") + .required(true) + .index(1), + ) + .arg( + clap::Arg::with_name("remote-ip") + .help("Specifies the IP of the proxy server peer") + .required(true) + .index(2), + ) + .arg( + clap::Arg::with_name("remote-port") + .help("Specifies the port of the proxy server peer") + .required(true) + .index(3), + ), + ) + .subcommand( + clap::SubCommand::with_name("remote") + .about("Registers a remote SOCKS5 proxy") + .arg( + clap::Arg::with_name("remote-ip") + .help("Specifies the IP of the remote proxy server") + .required(true) + .index(1), + ) + .arg( + clap::Arg::with_name("remote-port") + .help("Specifies the port the remote proxy server is listening on") + .required(true) + .index(2), + ) + .arg( + clap::Arg::with_name("username") + .help("Specifies the username for remote authentication") + .required(true) + .index(3), + ) + .arg( + clap::Arg::with_name("password") + .help("Specifies the password for remote authentication") + .required(true) + .index(4), + ), + ) + .subcommand( + clap::SubCommand::with_name("shadowsocks") + .about("Configure bundled Shadowsocks proxy") + .arg( + clap::Arg::with_name("remote-ip") + .help("Specifies the IP of the remote Shadowsocks server") + .required(true) + .index(1), + ) + .arg( + clap::Arg::with_name("remote-port") + .help("Specifies the port of the remote Shadowsocks server") + .default_value("443") + .index(2), + ) + .arg( + clap::Arg::with_name("password") + .help("Specifies the password on the remote Shadowsocks server") + .default_value("23#dfsbbb") + .index(3), + ) + .arg( + clap::Arg::with_name("cipher") + .help("Specifies the cipher to use") + .default_value("chacha20") + .possible_values(SHADOWSOCKS_CIPHERS) + .index(4), + ), + ) +} + +fn create_set_state_subcommand() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("state") + .about("Set bridge state") + .arg( + clap::Arg::with_name("state") + .help("Specifies whether a bridge should be used") + .index(1) + .possible_values(&["auto", "on", "off"]), + ) +} + +impl Bridge { + fn handle_set(matches: &clap::ArgMatches<'_>) -> Result<()> { + match matches.subcommand() { + ("location", Some(location_matches)) => { + Self::handle_set_bridge_location(location_matches) + } + ("custom", Some(custom_matches)) => { + Self::handle_bridge_set_custom_settings(custom_matches) + } + ("state", Some(set_matches)) => Self::handle_set_bridge_state(set_matches), + _ => unreachable!("unhandled command"), + } + } + + fn handle_get() -> Result<()> { + let mut rpc = new_rpc_client()?; + let settings = rpc.get_settings()?; + println!("Bridge state - {}", settings.get_bridge_state()); + match settings.get_bridge_settings() { + BridgeSettings::Custom(proxy) => { + match proxy { + openvpn::ProxySettings::Local(local_proxy) => { + Self::print_local_proxy(&local_proxy) + } + openvpn::ProxySettings::Remote(remote_proxy) => { + Self::print_remote_proxy(&remote_proxy) + } + openvpn::ProxySettings::Shadowsocks(shadowsocks_proxy) => { + Self::print_shadowsocks_proxy(&shadowsocks_proxy) + } + }; + } + BridgeSettings::Normal(constraints) => { + println!("Bridge constraints: {}", constraints); + } + }; + Ok(()) + } + + fn handle_set_bridge_location(matches: &clap::ArgMatches<'_>) -> Result<()> { + let location = location::get_constraint(matches); + let mut rpc = new_rpc_client()?; + rpc.set_bridge_settings(BridgeSettings::Normal(BridgeConstraints { location }))?; + Ok(()) + } + + fn handle_set_bridge_state(matches: &clap::ArgMatches<'_>) -> Result<()> { + let state = match matches.value_of("state").unwrap() { + "auto" => BridgeState::Auto, + "on" => BridgeState::On, + "off" => BridgeState::Off, + _ => unreachable!(), + }; + let mut rpc = new_rpc_client()?; + rpc.set_bridge_state(state)?; + Ok(()) + } + + fn handle_bridge_set_custom_settings(matches: &clap::ArgMatches<'_>) -> Result<()> { + if let Some(args) = matches.subcommand_matches("local") { + let local_port = + value_t!(args.value_of("local-port"), u16).unwrap_or_else(|e| e.exit()); + let remote_ip = + value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit()); + let remote_port = + value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit()); + + let proxy = openvpn::LocalProxySettings { + port: local_port, + peer: SocketAddr::new(remote_ip, remote_port), + }; + + let packed_proxy = openvpn::ProxySettings::Local(proxy); + + if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { + panic!(error); + } + + let mut rpc = new_rpc_client()?; + rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))?; + } else if let Some(args) = matches.subcommand_matches("remote") { + let remote_ip = + value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit()); + let remote_port = + value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit()); + let username = args.value_of("username"); + let password = args.value_of("password"); + + let auth = match (username, password) { + (Some(username), Some(password)) => Some(openvpn::ProxyAuth { + username: username.to_string(), + password: password.to_string(), + }), + _ => None, + }; + + let proxy = openvpn::RemoteProxySettings { + address: SocketAddr::new(remote_ip, remote_port), + auth, + }; + + let packed_proxy = openvpn::ProxySettings::Remote(proxy); + + if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { + panic!(error); + } + + let mut rpc = new_rpc_client()?; + rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))?; + } else if let Some(args) = matches.subcommand_matches("shadowsocks") { + let remote_ip = + value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit()); + let remote_port = + value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit()); + let password = args.value_of("password").unwrap().to_string(); + let cipher = args.value_of("cipher").unwrap().to_string(); + + let proxy = openvpn::ShadowsocksProxySettings { + peer: SocketAddr::new(remote_ip, remote_port), + password, + cipher, + }; + + let packed_proxy = openvpn::ProxySettings::Shadowsocks(proxy); + + if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { + panic!(error); + } + + let mut rpc = new_rpc_client()?; + rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))?; + } else { + unreachable!("unhandled proxy type"); + } + + println!("proxy details have been updated"); + Ok(()) + } + + fn print_local_proxy(proxy: &openvpn::LocalProxySettings) { + println!("proxy: local"); + println!(" local port: {}", proxy.port); + println!(" peer IP: {}", proxy.peer.ip()); + println!(" peer port: {}", proxy.peer.port()); + } + + fn print_remote_proxy(proxy: &openvpn::RemoteProxySettings) { + println!("proxy: remote"); + println!(" server IP: {}", proxy.address.ip()); + println!(" server port: {}", proxy.address.port()); + + if let Some(ref auth) = proxy.auth { + println!(" auth username: {}", auth.username); + println!(" auth password: {}", auth.password); + } else { + println!(" auth: none"); + } + } + + fn print_shadowsocks_proxy(proxy: &openvpn::ShadowsocksProxySettings) { + println!("proxy: Shadowsocks"); + println!(" peer IP: {}", proxy.peer.ip()); + println!(" peer port: {}", proxy.peer.port()); + println!(" password: {}", proxy.password); + println!(" cipher: {}", proxy.cipher); + } + + fn list_bridge_relays() -> Result<()> { + let mut rpc = new_rpc_client()?; + let mut locations = rpc.get_relay_locations()?; + + locations.countries = locations + .countries + .into_iter() + .filter_map(|mut country| { + country.cities = country + .cities + .into_iter() + .filter_map(|mut city| { + city.relays.retain(|relay| !relay.bridges.is_empty()); + if !city.relays.is_empty() { + Some(city) + } else { + None + } + }) + .collect(); + if !country.cities.is_empty() { + Some(country) + } else { + None + } + }) + .collect(); + + for mut country in locations.countries { + country.cities.sort_by(|c1, c2| c1.name.cmp(&c2.name)); + println!("{} ({})", country.name, country.code); + for city in &country.cities { + println!( + "\t{} ({}) @ {:.5}°N, {:.5}°W", + city.name, city.code, city.latitude, city.longitude + ); + } + println!(); + } + Ok(()) + } +} diff --git a/mullvad-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs index 43e1968065..83bfd47883 100644 --- a/mullvad-cli/src/cmds/mod.rs +++ b/mullvad-cli/src/cmds/mod.rs @@ -7,6 +7,9 @@ pub use self::account::Account; mod auto_connect; pub use self::auto_connect::AutoConnect; +mod bridge; +pub use self::bridge::Bridge; + mod status; pub use self::status::Status; @@ -37,6 +40,7 @@ pub fn get_commands() -> HashMap<&'static str, Box<dyn Command>> { Box::new(Account), Box::new(AutoConnect), Box::new(BlockWhenDisconnected), + Box::new(Bridge), Box::new(Connect), Box::new(Disconnect), Box::new(Lan), diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index 5cc8f2815b..965107057d 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -1,4 +1,4 @@ -use crate::{new_rpc_client, Command, Error, Result}; +use crate::{location, new_rpc_client, Command, Error, Result}; use clap::{value_t, values_t}; use std::{ io::{self, BufRead}, @@ -8,8 +8,8 @@ use std::{ use mullvad_types::{ relay_constraints::{ - Constraint, LocationConstraint, OpenVpnConstraints, RelayConstraintsUpdate, - RelaySettingsUpdate, TunnelConstraints, WireguardConstraints, + Constraint, OpenVpnConstraints, RelayConstraintsUpdate, RelaySettingsUpdate, + TunnelConstraints, WireguardConstraints, }, ConnectionConfig, CustomTunnelEndpoint, }; @@ -109,31 +109,9 @@ impl Command for Relay { ) ) .subcommand( - clap::SubCommand::with_name("location") - .about( - "Set country or city to select relays from. Use the 'list' \ - command to show available alternatives.", - ) - .arg( - clap::Arg::with_name("country") - .help( - "The two letter country code, or 'any' for no preference.", - ) - .required(true) - .index(1) - .validator(country_code_validator), - ) - .arg( - clap::Arg::with_name("city") - .help("The three letter city code") - .index(2) - .validator(city_code_validator), - ) - .arg( - clap::Arg::with_name("hostname") - .help("The relay hostname") - .index(3), - ), + location::get_subcommand() + .about("Set country or city to select relays from. Use the 'list' \ + command to show available alternatives.") ) .subcommand( clap::SubCommand::with_name("tunnel") @@ -289,37 +267,7 @@ impl Relay { } fn set_location(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { - let country = matches.value_of("country").unwrap(); - let city = matches.value_of("city"); - let hostname = matches.value_of("hostname"); - - let location_constraint = match (country, city, hostname) { - ("any", None, None) => Constraint::Any, - ("any", ..) => clap::Error::with_description( - "City can't be given when selecting 'any' country", - clap::ErrorKind::InvalidValue, - ) - .exit(), - (country, None, None) => { - Constraint::Only(LocationConstraint::Country(country.to_owned())) - } - (country, Some(city), None) => Constraint::Only(LocationConstraint::City( - country.to_owned(), - city.to_owned(), - )), - (country, Some(city), Some(hostname)) => { - Constraint::Only(LocationConstraint::Hostname( - country.to_owned(), - city.to_owned(), - hostname.to_owned(), - )) - } - (..) => clap::Error::with_description( - "Invalid country, city and hostname combination given", - clap::ErrorKind::InvalidValue, - ) - .exit(), - }; + let location_constraint = location::get_constraint(matches); self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { location: Some(location_constraint), @@ -409,19 +357,3 @@ fn parse_protocol_constraint(raw_protocol: &str) -> Constraint<TransportProtocol _ => unreachable!(), } } - -fn country_code_validator(code: String) -> ::std::result::Result<(), String> { - if code.len() == 2 || code == "any" { - Ok(()) - } else { - Err(String::from("Country codes must be two letters, or 'any'.")) - } -} - -fn city_code_validator(code: String) -> ::std::result::Result<(), String> { - if code.len() == 3 { - Ok(()) - } else { - Err(String::from("City codes must be three letters")) - } -} diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index c02a88d03a..136290d5bb 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -2,9 +2,6 @@ use crate::{new_rpc_client, Command, Result}; use clap::value_t; use mullvad_types::settings::TunnelOptions; -use talpid_types::net::openvpn::{self, SHADOWSOCKS_CIPHERS}; - -use std::net::{IpAddr, SocketAddr}; pub struct Tunnel; @@ -67,7 +64,6 @@ fn create_openvpn_subcommand() -> clap::App<'static, 'static> { .about("Manage options for OpenVPN tunnels") .setting(clap::AppSettings::SubcommandRequiredElseHelp) .subcommand(create_openvpn_mssfix_subcommand()) - .subcommand(create_openvpn_proxy_subcommand()) } fn create_openvpn_mssfix_subcommand() -> clap::App<'static, 'static> { @@ -81,97 +77,6 @@ fn create_openvpn_mssfix_subcommand() -> clap::App<'static, 'static> { ) } -fn create_openvpn_proxy_subcommand() -> clap::App<'static, 'static> { - clap::SubCommand::with_name("proxy") - .about("Configure a SOCKS5 proxy") - .setting(clap::AppSettings::SubcommandRequiredElseHelp) - .subcommand(clap::SubCommand::with_name("get")) - .subcommand(clap::SubCommand::with_name("unset")) - .subcommand( - clap::SubCommand::with_name("set") - .setting(clap::AppSettings::SubcommandRequiredElseHelp) - .subcommand( - clap::SubCommand::with_name("local") - .about("Registers a local SOCKS5 proxy") - .arg( - clap::Arg::with_name("local-port") - .help("Specifies the port the local proxy server is listening on") - .required(true) - .index(1), - ) - .arg( - clap::Arg::with_name("remote-ip") - .help("Specifies the IP of the proxy server peer") - .required(true) - .index(2), - ) - .arg( - clap::Arg::with_name("remote-port") - .help("Specifies the port of the proxy server peer") - .required(true) - .index(3), - ), - ) - .subcommand( - clap::SubCommand::with_name("remote") - .about("Registers a remote SOCKS5 proxy") - .arg( - clap::Arg::with_name("remote-ip") - .help("Specifies the IP of the remote proxy server") - .required(true) - .index(1), - ) - .arg( - clap::Arg::with_name("remote-port") - .help("Specifies the port the remote proxy server is listening on") - .required(true) - .index(2), - ) - .arg( - clap::Arg::with_name("username") - .help("Specifies the username for remote authentication") - .required(true) - .index(3), - ) - .arg( - clap::Arg::with_name("password") - .help("Specifies the password for remote authentication") - .required(true) - .index(4), - ), - ) - .subcommand( - clap::SubCommand::with_name("shadowsocks") - .about("Configure bundled Shadowsocks proxy") - .arg( - clap::Arg::with_name("remote-ip") - .help("Specifies the IP of the remote Shadowsocks server") - .required(true) - .index(1), - ) - .arg( - clap::Arg::with_name("remote-port") - .help("Specifies the port of the remote Shadowsocks server") - .default_value("443") - .index(2), - ) - .arg( - clap::Arg::with_name("password") - .help("Specifies the password on the remote Shadowsocks server") - .default_value("23#dfsbbb") - .index(3), - ) - .arg( - clap::Arg::with_name("cipher") - .help("Specifies the cipher to use") - .default_value("chacha20") - .possible_values(SHADOWSOCKS_CIPHERS) - .index(4), - ), - ), - ) -} - fn create_ipv6_subcommand() -> clap::App<'static, 'static> { clap::SubCommand::with_name("ipv6") .setting(clap::AppSettings::SubcommandRequiredElseHelp) @@ -190,7 +95,6 @@ impl Tunnel { fn handle_openvpn_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { ("mssfix", Some(mssfix_matches)) => Self::handle_openvpn_mssfix_cmd(mssfix_matches), - ("proxy", Some(proxy_matches)) => Self::handle_openvpn_proxy_cmd(proxy_matches), _ => unreachable!("unhandled command"), } } @@ -204,15 +108,6 @@ impl Tunnel { } } - fn handle_openvpn_proxy_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { - match matches.subcommand() { - ("get", Some(_)) => Self::process_openvpn_proxy_get(), - ("unset", Some(_)) => Self::process_openvpn_proxy_unset(), - ("set", Some(set_matches)) => Self::process_openvpn_proxy_set(set_matches), - _ => unreachable!("unhandled command"), - } - } - fn handle_wireguard_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { ("mtu", Some(matches)) => match matches.subcommand() { @@ -323,141 +218,6 @@ impl Tunnel { Ok(()) } - fn process_openvpn_proxy_get() -> Result<()> { - let tunnel_options = Self::get_tunnel_options()?; - if let Some(proxy) = tunnel_options.openvpn.proxy { - if let openvpn::ProxySettings::Local(local_proxy) = proxy { - Self::print_local_proxy(&local_proxy) - } else if let openvpn::ProxySettings::Remote(remote_proxy) = proxy { - Self::print_remote_proxy(&remote_proxy) - } else if let openvpn::ProxySettings::Shadowsocks(shadowsocks_proxy) = proxy { - Self::print_shadowsocks_proxy(&shadowsocks_proxy) - } else { - unreachable!("unhandled proxy type"); - } - } else { - println!("proxy: unset"); - } - Ok(()) - } - - fn print_local_proxy(proxy: &openvpn::LocalProxySettings) { - println!("proxy: local"); - println!(" local port: {}", proxy.port); - println!(" peer IP: {}", proxy.peer.ip()); - println!(" peer port: {}", proxy.peer.port()); - } - - fn print_remote_proxy(proxy: &openvpn::RemoteProxySettings) { - println!("proxy: remote"); - println!(" server IP: {}", proxy.address.ip()); - println!(" server port: {}", proxy.address.port()); - - if let Some(ref auth) = proxy.auth { - println!(" auth username: {}", auth.username); - println!(" auth password: {}", auth.password); - } else { - println!(" auth: none"); - } - } - - fn print_shadowsocks_proxy(proxy: &openvpn::ShadowsocksProxySettings) { - println!("proxy: Shadowsocks"); - println!(" peer IP: {}", proxy.peer.ip()); - println!(" peer port: {}", proxy.peer.port()); - println!(" password: {}", proxy.password); - println!(" cipher: {}", proxy.cipher); - } - - fn process_openvpn_proxy_unset() -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_openvpn_proxy(None)?; - println!("proxy details have been unset"); - Ok(()) - } - - fn process_openvpn_proxy_set(matches: &clap::ArgMatches<'_>) -> Result<()> { - if let Some(args) = matches.subcommand_matches("local") { - let local_port = - value_t!(args.value_of("local-port"), u16).unwrap_or_else(|e| e.exit()); - let remote_ip = - value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit()); - let remote_port = - value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit()); - - let proxy = openvpn::LocalProxySettings { - port: local_port, - peer: SocketAddr::new(remote_ip, remote_port), - }; - - let packed_proxy = openvpn::ProxySettings::Local(proxy); - - if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { - panic!(error); - } - - let mut rpc = new_rpc_client()?; - rpc.set_openvpn_proxy(Some(packed_proxy))?; - } else if let Some(args) = matches.subcommand_matches("remote") { - let remote_ip = - value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit()); - let remote_port = - value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit()); - let username = args.value_of("username"); - let password = args.value_of("password"); - - let auth = match (username, password) { - (Some(username), Some(password)) => Some(openvpn::ProxyAuth { - username: username.to_string(), - password: password.to_string(), - }), - _ => None, - }; - - let proxy = openvpn::RemoteProxySettings { - address: SocketAddr::new(remote_ip, remote_port), - auth, - }; - - let packed_proxy = openvpn::ProxySettings::Remote(proxy); - - if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { - panic!(error); - } - - let mut rpc = new_rpc_client()?; - rpc.set_openvpn_proxy(Some(packed_proxy))?; - } else if let Some(args) = matches.subcommand_matches("shadowsocks") { - let remote_ip = - value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit()); - let remote_port = - value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit()); - let password = args.value_of("password").unwrap().to_string(); - let cipher = args.value_of("cipher").unwrap().to_string(); - - let proxy = openvpn::ShadowsocksProxySettings { - peer: SocketAddr::new(remote_ip, remote_port), - password, - cipher, - }; - - let packed_proxy = openvpn::ProxySettings::Shadowsocks(proxy); - - if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { - panic!(error); - } - - let mut rpc = new_rpc_client()?; - rpc.set_openvpn_proxy(Some(packed_proxy))?; - } else { - unreachable!("unhandled proxy type"); - } - - println!("proxy details have been updated"); - println!("note: The OpenVPN tunnel constraints have been updated to use TCP"); - Ok(()) - } - fn process_ipv6_get() -> Result<()> { let tunnel_options = Self::get_tunnel_options()?; println!( diff --git a/mullvad-cli/src/location.rs b/mullvad-cli/src/location.rs new file mode 100644 index 0000000000..10b27b61d8 --- /dev/null +++ b/mullvad-cli/src/location.rs @@ -0,0 +1,69 @@ +use mullvad_types::relay_constraints::{Constraint, LocationConstraint}; + +pub fn get_subcommand() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("location") + .arg( + clap::Arg::with_name("country") + .help("The two letter country code, or 'any' for no preference.") + .required(true) + .index(1) + .validator(country_code_validator), + ) + .arg( + clap::Arg::with_name("city") + .help("The three letter city code") + .index(2) + .validator(city_code_validator), + ) + .arg( + clap::Arg::with_name("hostname") + .help("The hostname") + .index(3), + ) +} + +pub fn get_constraint(matches: &clap::ArgMatches<'_>) -> Constraint<LocationConstraint> { + let country = matches.value_of("country").unwrap(); + let city = matches.value_of("city"); + let hostname = matches.value_of("hostname"); + + match (country, city, hostname) { + ("any", None, None) => Constraint::Any, + ("any", ..) => clap::Error::with_description( + "City can't be given when selecting 'any' country", + clap::ErrorKind::InvalidValue, + ) + .exit(), + (country, None, None) => Constraint::Only(LocationConstraint::Country(country.to_owned())), + (country, Some(city), None) => Constraint::Only(LocationConstraint::City( + country.to_owned(), + city.to_owned(), + )), + (country, Some(city), Some(hostname)) => Constraint::Only(LocationConstraint::Hostname( + country.to_owned(), + city.to_owned(), + hostname.to_owned(), + )), + (..) => clap::Error::with_description( + "Invalid country, city and hostname combination given", + clap::ErrorKind::InvalidValue, + ) + .exit(), + } +} + +fn country_code_validator(code: String) -> ::std::result::Result<(), String> { + if code.len() == 2 || code == "any" { + Ok(()) + } else { + Err(String::from("Country codes must be two letters, or 'any'.")) + } +} + +fn city_code_validator(code: String) -> ::std::result::Result<(), String> { + if code.len() == 3 { + Ok(()) + } else { + Err(String::from("City codes must be three letters")) + } +} diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs index 3dc1879fb8..4abe16224c 100644 --- a/mullvad-cli/src/main.rs +++ b/mullvad-cli/src/main.rs @@ -14,6 +14,7 @@ use std::io; use talpid_types::ErrorExt; mod cmds; +mod location; pub const PRODUCT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/product-version.txt")); |
