Skip to content

Commit

Permalink
Add authentication with username+password for SOCKS5 access method
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
MarkusPettersson98 committed Sep 29, 2023
1 parent 414e867 commit 1742c27
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 42 deletions.
47 changes: 38 additions & 9 deletions mullvad-api/src/https_client_with_sni.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -201,6 +211,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)]
Expand All @@ -213,6 +230,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 {
Expand All @@ -228,13 +247,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,
})
}
},
},
Expand Down
75 changes: 61 additions & 14 deletions mullvad-cli/src/cmds/api_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
},
},
Expand Down Expand Up @@ -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)]
Expand All @@ -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 {
Expand Down Expand Up @@ -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]
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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) => {
Expand Down
9 changes: 7 additions & 2 deletions mullvad-management-interface/proto/management_interface.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
63 changes: 47 additions & 16 deletions mullvad-management-interface/src/types/conversions/access_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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;

Expand Down
24 changes: 23 additions & 1 deletion mullvad-types/src/access_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand All @@ -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 {
Expand Down

0 comments on commit 1742c27

Please sign in to comment.