summaryrefslogtreecommitdiffhomepage
path: root/mullvad-cli/src
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2023-11-17 11:10:16 +0100
committerDavid Lönnhager <david.l@mullvad.net>2023-11-17 11:10:16 +0100
commitd798fd5872e311fffeccd9930a45fdb2ed7eb2ea (patch)
treee01cb6d76e8b3ee10c530bb85767b533fa1bebc3 /mullvad-cli/src
parent0a82036e2b49dbd42819d36860b00289b3219a6b (diff)
parentb83735c6a569baecd2272d0a78721fe7998a47ce (diff)
downloadmullvadvpn-d798fd5872e311fffeccd9930a45fdb2ed7eb2ea.tar.xz
mullvadvpn-d798fd5872e311fffeccd9930a45fdb2ed7eb2ea.zip
Merge branch 'add-settings-patching' into main
Diffstat (limited to 'mullvad-cli/src')
-rw-r--r--mullvad-cli/src/cmds/import_settings.rs101
-rw-r--r--mullvad-cli/src/cmds/mod.rs1
-rw-r--r--mullvad-cli/src/main.rs7
3 files changed, 109 insertions, 0 deletions
diff --git a/mullvad-cli/src/cmds/import_settings.rs b/mullvad-cli/src/cmds/import_settings.rs
new file mode 100644
index 0000000000..a354eb4b95
--- /dev/null
+++ b/mullvad-cli/src/cmds/import_settings.rs
@@ -0,0 +1,101 @@
+use anyhow::{anyhow, Context, Result};
+use mullvad_management_interface::MullvadProxyClient;
+use std::{
+ fs::File,
+ io::{stdin, BufRead, BufReader},
+ path::Path,
+};
+
+/// Maximum size of a settings patch. Bigger files/streams cause the read to fail.
+const MAX_PATCH_BYTES: usize = 10 * 1024;
+
+/// If source is specified, read from the provided file and send it as a settings patch to the daemon.
+/// Otherwise, read the patch from standard input.
+pub async fn handle(source: String) -> Result<()> {
+ let json_blob = tokio::task::spawn_blocking(|| get_blob(source))
+ .await
+ .unwrap()?;
+
+ let mut rpc = MullvadProxyClient::new().await?;
+ rpc.apply_json_settings(json_blob)
+ .await
+ .context("Error applying patch")?;
+
+ println!("Settings applied");
+
+ Ok(())
+}
+
+fn get_blob(source: String) -> Result<String> {
+ match source.as_str() {
+ "-" => read_settings_from_stdin().context("Failed to read from stdin"),
+ _ => read_settings_from_file(source).context("Failed to read from path: {source}"),
+ }
+}
+
+/// Read settings from standard input
+fn read_settings_from_stdin() -> Result<String> {
+ read_settings_from_reader(BufReader::new(stdin()))
+}
+
+/// Read settings from a path
+fn read_settings_from_file(path: impl AsRef<Path>) -> Result<String> {
+ read_settings_from_reader(BufReader::new(File::open(path)?))
+}
+
+/// Read until EOF or until newline when the last pair of braces has been closed
+fn read_settings_from_reader(mut reader: impl BufRead) -> Result<String> {
+ let mut buf = [0u8; MAX_PATCH_BYTES];
+
+ let mut was_open = false;
+ let mut close_after_newline = false;
+ let mut brace_count: usize = 0;
+ let mut cursor_pos = 0;
+
+ loop {
+ let Some(cursor) = buf.get_mut(cursor_pos..) else {
+ return Err(anyhow!(
+ "Patch too long: maximum length is {MAX_PATCH_BYTES} bytes"
+ ));
+ };
+
+ let prev_cursor_pos = cursor_pos;
+ let read_n = reader.read(cursor)?;
+ if read_n == 0 {
+ // EOF
+ break;
+ }
+ cursor_pos += read_n;
+
+ let additional_bytes = &buf[prev_cursor_pos..cursor_pos];
+
+ if !close_after_newline {
+ for next in additional_bytes {
+ match next {
+ b'{' => brace_count += 1,
+ b'}' => {
+ brace_count = brace_count.checked_sub(1).with_context(|| {
+ // exit: too many closing braces
+ "syntax error: unexpected '}'"
+ })?
+ }
+ _ => (),
+ }
+ was_open |= brace_count > 0;
+ }
+ if brace_count == 0 && was_open {
+ // complete settings
+ close_after_newline = true;
+ }
+ }
+
+ if close_after_newline && additional_bytes.contains(&b'\n') {
+ // done
+ break;
+ }
+ }
+
+ Ok(std::str::from_utf8(&buf[0..cursor_pos])
+ .context("settings must be utf8 encoded")?
+ .to_owned())
+}
diff --git a/mullvad-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs
index 43d224233e..7944e8bdc0 100644
--- a/mullvad-cli/src/cmds/mod.rs
+++ b/mullvad-cli/src/cmds/mod.rs
@@ -9,6 +9,7 @@ pub mod beta_program;
pub mod bridge;
pub mod custom_list;
pub mod dns;
+pub mod import_settings;
pub mod lan;
pub mod lockdown;
pub mod obfuscation;
diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs
index 7a09a4eebd..d1c518119c 100644
--- a/mullvad-cli/src/main.rs
+++ b/mullvad-cli/src/main.rs
@@ -133,6 +133,12 @@ enum Cli {
/// Manage custom lists
#[clap(subcommand)]
CustomList(custom_list::CustomList),
+
+ /// Apply a JSON patch
+ ImportSettings {
+ /// File to read from. If this is "-", read from standard input
+ file: String,
+ },
}
#[tokio::main]
@@ -160,6 +166,7 @@ async fn main() -> Result<()> {
Cli::SplitTunnel(cmd) => cmd.handle().await,
Cli::Status { cmd, args } => status::handle(cmd, args).await,
Cli::CustomList(cmd) => cmd.handle().await,
+ Cli::ImportSettings { file } => import_settings::handle(file).await,
#[cfg(all(unix, not(target_os = "android")))]
Cli::ShellCompletions { shell, dir } => {