diff options
| author | Markus Pettersson <markus.pettersson@mullvad.net> | 2023-09-28 10:41:31 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2023-10-09 14:40:15 +0200 |
| commit | ccc5aaaff23291366b4c0074f57a5096b94cdfce (patch) | |
| tree | dd30da0b9ac7b1650cb72bbdae6372924a7c18dd | |
| parent | 998fd39aaebc1435065f1f4886f4bd40f5889e5a (diff) | |
| download | mullvadvpn-ccc5aaaff23291366b4c0074f57a5096b94cdfce.tar.xz mullvadvpn-ccc5aaaff23291366b4c0074f57a5096b94cdfce.zip | |
Add authentication with username+password for SOCKS5 access method
Add the option to authenticate against remote SOCKS5 proxies with a
username+password combination. It was an oversight that this was not
added from the start.
| -rw-r--r-- | mullvad-api/src/https_client_with_sni.rs | 47 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/api_access.rs | 75 | ||||
| -rw-r--r-- | mullvad-management-interface/proto/management_interface.proto | 9 | ||||
| -rw-r--r-- | mullvad-management-interface/src/types/conversions/access_method.rs | 63 | ||||
| -rw-r--r-- | mullvad-types/src/access_method.rs | 24 |
5 files changed, 176 insertions, 42 deletions
diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs index 47b6923d37..17d9f7f0d8 100644 --- a/mullvad-api/src/https_client_with_sni.rs +++ b/mullvad-api/src/https_client_with_sni.rs @@ -125,11 +125,21 @@ impl InnerConnectionMode { InnerConnectionMode::Socks5(socks) => { let first_hop = socks.peer; let make_proxy_stream = |tcp_stream| async { - tokio_socks::tcp::Socks5Stream::connect_with_socket(tcp_stream, addr) - .await - .map_err(|error| { - io::Error::new(io::ErrorKind::Other, format!("SOCKS error: {error}")) - }) + match socks.authentication { + SocksAuth::None => { + tokio_socks::tcp::Socks5Stream::connect_with_socket(tcp_stream, addr) + .await + } + SocksAuth::Password { username, password } => { + tokio_socks::tcp::Socks5Stream::connect_with_password_and_socket( + tcp_stream, addr, &username, &password, + ) + .await + } + } + .map_err(|error| { + io::Error::new(io::ErrorKind::Other, format!("SOCKS error: {error}")) + }) }; Self::connect_proxied( first_hop, @@ -207,6 +217,13 @@ impl From<ParsedShadowsocksConfig> for ServerConfig { #[derive(Clone)] struct SocksConfig { peer: SocketAddr, + authentication: SocksAuth, +} + +#[derive(Clone)] +pub enum SocksAuth { + None, + Password { username: String, password: String }, } #[derive(err_derive::Error, Debug)] @@ -219,6 +236,8 @@ impl TryFrom<ApiConnectionMode> for InnerConnectionMode { type Error = ProxyConfigError; fn try_from(config: ApiConnectionMode) -> Result<Self, Self::Error> { + use mullvad_types::access_method; + use std::net::Ipv4Addr; Ok(match config { ApiConnectionMode::Direct => InnerConnectionMode::Direct, ApiConnectionMode::Proxied(proxy_settings) => match proxy_settings { @@ -234,13 +253,23 @@ impl TryFrom<ApiConnectionMode> for InnerConnectionMode { }) } ProxyConfig::Socks(config) => match config { - mullvad_types::access_method::Socks5::Local(config) => { + access_method::Socks5::Local(config) => { InnerConnectionMode::Socks5(SocksConfig { - peer: SocketAddr::new("127.0.0.1".parse().unwrap(), config.port), + peer: SocketAddr::new(IpAddr::from(Ipv4Addr::LOCALHOST), config.port), + authentication: SocksAuth::None, }) } - mullvad_types::access_method::Socks5::Remote(config) => { - InnerConnectionMode::Socks5(SocksConfig { peer: config.peer }) + access_method::Socks5::Remote(config) => { + let authentication = match config.authentication { + Some(access_method::SocksAuth { username, password }) => { + SocksAuth::Password { username, password } + } + None => SocksAuth::None, + }; + InnerConnectionMode::Socks5(SocksConfig { + peer: config.peer, + authentication, + }) } }, }, diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index a3b17aca0b..75fe7c44bf 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -121,8 +121,20 @@ impl ApiAccess { mullvad_types::access_method::Socks5::Remote(remote) => { let ip = cmd.params.ip.unwrap_or(remote.peer.ip()).to_string(); let port = cmd.params.port.unwrap_or(remote.peer.port()); - mullvad_types::access_method::Socks5Remote::from_args(ip, port) - .map(AccessMethod::from) + match remote.authentication { + None => mullvad_types::access_method::Socks5Remote::from_args(ip, port), + Some(mullvad_types::access_method::SocksAuth { + username, + password, + }) => { + let username = cmd.params.username.unwrap_or(username); + let password = cmd.params.password.unwrap_or(password); + mullvad_types::access_method::Socks5Remote::from_args_with_password( + ip, port, username, password, + ) + } + } + .map(AccessMethod::from) } }, }, @@ -241,6 +253,8 @@ pub enum AddSocks5Commands { remote_ip: IpAddr, /// The port of the remote proxy server remote_port: u16, + #[clap(flatten)] + authentication: Option<SocksAuthentication>, /// Disable the use of this custom access method. It has to be manually /// enabled at a later stage to be used when accessing the Mullvad API. #[arg(default_value_t = false, short, long)] @@ -263,6 +277,16 @@ pub enum AddSocks5Commands { }, } +#[derive(Args, Debug, Clone)] +pub struct SocksAuthentication { + /// Username for authentication against a remote SOCKS5 proxy + #[arg(short, long)] + username: String, + /// Password for authentication against a remote SOCKS5 proxy + #[arg(short, long)] + password: String, +} + impl AddCustomCommands { fn name(&self) -> &str { match self { @@ -319,7 +343,10 @@ pub struct EditParams { /// Name of the API access method in the Mullvad client [All] #[arg(long)] name: Option<String>, - /// Password for authentication [Shadowsocks] + /// Username for authentication [Socks5 (Remote proxy)] + #[arg(long)] + username: Option<String>, + /// Password for authentication [Socks5 (Remote proxy), Shadowsocks] #[arg(long)] password: Option<String>, /// Cipher to use [Shadowsocks] @@ -344,7 +371,7 @@ mod conversions { use anyhow::{anyhow, Error}; use mullvad_types::access_method as daemon_types; - use super::{AddCustomCommands, AddSocks5Commands}; + use super::{AddCustomCommands, AddSocks5Commands, SocksAuthentication}; impl TryFrom<AddCustomCommands> for daemon_types::AccessMethod { type Error = Error; @@ -373,18 +400,31 @@ mod conversions { AddSocks5Commands::Remote { remote_ip, remote_port, + authentication, name: _, disabled: _, } => { - println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}"); - let socks_proxy = daemon_types::Socks5::Remote( - daemon_types::Socks5Remote::from_args( - remote_ip.to_string(), - remote_port, - ) - .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?, - ); - daemon_types::AccessMethod::from(socks_proxy) + match authentication { + Some(SocksAuthentication { username, password }) => { + println!("Adding SOCKS5-proxy: {username}:{password}@{remote_ip}:{remote_port}"); + daemon_types::Socks5Remote::from_args_with_password( + remote_ip.to_string(), + remote_port, + username, + password + ) + } + None => { + println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}"); + daemon_types::Socks5Remote::from_args( + remote_ip.to_string(), + remote_port, + ) + } + } + .map(daemon_types::Socks5::Remote) + .map(daemon_types::AccessMethod::from) + .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))? } }, AddCustomCommands::Shadowsocks { @@ -415,7 +455,7 @@ mod conversions { /// Pretty printing of [`ApiAccessMethod`]s mod pp { use mullvad_types::access_method::{ - AccessMethod, AccessMethodSetting, CustomAccessMethod, Socks5, + AccessMethod, AccessMethodSetting, CustomAccessMethod, Socks5, SocksAuth, }; pub struct ApiAccessMethodFormatter<'a> { @@ -462,6 +502,13 @@ mod pp { writeln!(f)?; print_option!("Protocol", "Socks5"); print_option!("Peer", remote.peer); + match &remote.authentication { + Some(SocksAuth { username, password }) => { + print_option!("Username", username); + print_option!("Password", password); + } + None => (), + } Ok(()) } Socks5::Local(local) => { diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index c9b9bc5138..1bbb7f5012 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -342,9 +342,14 @@ message AccessMethod { uint32 port = 2; uint32 local_port = 3; } + message SocksAuth { + string username = 1; + string password = 2; + } message Socks5Remote { - string ip = 2; - uint32 port = 3; + string ip = 1; + uint32 port = 2; + SocksAuth authentication = 3; } message Shadowsocks { string ip = 1; diff --git a/mullvad-management-interface/src/types/conversions/access_method.rs b/mullvad-management-interface/src/types/conversions/access_method.rs index 0f39d34e9e..8907c4da29 100644 --- a/mullvad-management-interface/src/types/conversions/access_method.rs +++ b/mullvad-management-interface/src/types/conversions/access_method.rs @@ -45,7 +45,7 @@ mod data { use crate::types::{proto, FromProtobufTypeError}; use mullvad_types::access_method::{ AccessMethod, AccessMethodSetting, BuiltInAccessMethod, CustomAccessMethod, Id, - Shadowsocks, Socks5, Socks5Local, Socks5Remote, + Shadowsocks, Socks5, Socks5Local, Socks5Remote, SocksAuth, }; impl TryFrom<proto::AccessMethodSetting> for AccessMethodSetting { @@ -154,13 +154,24 @@ mod data { type Error = FromProtobufTypeError; fn try_from(value: proto::access_method::Socks5Remote) -> Result<Self, Self::Error> { - Socks5Remote::from_args(value.ip, value.port as u16) - .ok_or({ - FromProtobufTypeError::InvalidArgument( - "Could not parse Socks5 (remote) message from protobuf", - ) - }) - .map(AccessMethod::from) + let proto::access_method::Socks5Remote { + ip, + port, + authentication, + } = value; + let port = port as u16; + match authentication.map(SocksAuth::from) { + Some(SocksAuth { username, password }) => { + Socks5Remote::from_args_with_password(ip, port, username, password) + } + None => Socks5Remote::from_args(ip, port), + } + .ok_or({ + FromProtobufTypeError::InvalidArgument( + "Could not parse Socks5 (remote) message from protobuf", + ) + }) + .map(AccessMethod::from) } } @@ -214,14 +225,16 @@ mod data { }, ) } - CustomAccessMethod::Socks5(Socks5::Remote(Socks5Remote { peer })) => { - proto::access_method::AccessMethod::Socks5remote( - proto::access_method::Socks5Remote { - ip: peer.ip().to_string(), - port: peer.port() as u32, - }, - ) - } + CustomAccessMethod::Socks5(Socks5::Remote(Socks5Remote { + peer, + authentication, + })) => proto::access_method::AccessMethod::Socks5remote( + proto::access_method::Socks5Remote { + ip: peer.ip().to_string(), + port: peer.port() as u32, + authentication: authentication.map(proto::access_method::SocksAuth::from), + }, + ), }; proto::AccessMethod { @@ -248,6 +261,24 @@ mod data { } } + impl From<SocksAuth> for proto::access_method::SocksAuth { + fn from(value: SocksAuth) -> Self { + proto::access_method::SocksAuth { + username: value.username, + password: value.password, + } + } + } + + impl From<proto::access_method::SocksAuth> for SocksAuth { + fn from(value: proto::access_method::SocksAuth) -> Self { + Self { + username: value.username, + password: value.password, + } + } + } + impl TryFrom<&proto::AccessMethodSetting> for AccessMethodSetting { type Error = FromProtobufTypeError; diff --git a/mullvad-types/src/access_method.rs b/mullvad-types/src/access_method.rs index a300072c82..30c1f25192 100644 --- a/mullvad-types/src/access_method.rs +++ b/mullvad-types/src/access_method.rs @@ -209,6 +209,13 @@ pub struct Socks5Local { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct Socks5Remote { pub peer: SocketAddr, + pub authentication: Option<SocksAuth>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct SocksAuth { + pub username: String, + pub password: String, } impl AccessMethod { @@ -262,7 +269,10 @@ impl Socks5Local { impl Socks5Remote { pub fn new(peer: SocketAddr) -> Self { - Self { peer } + Self { + peer, + authentication: None, + } } /// Like [new()], but tries to parse `ip` and `port` into a [`std::net::SocketAddr`] for you. @@ -272,6 +282,18 @@ impl Socks5Remote { let peer = SocketAddr::new(peer_ip, port); Some(Self::new(peer)) } + + /// Like [from_args()], but with authentication. + pub fn from_args_with_password( + ip: String, + port: u16, + username: String, + password: String, + ) -> Option<Self> { + let mut socks = Self::from_args(ip, port)?; + socks.authentication = Some(SocksAuth { username, password }); + Some(socks) + } } impl From<BuiltInAccessMethod> for AccessMethod { |
