diff options
| author | David Lönnhager <david.l@mullvad.net> | 2023-11-17 11:10:16 +0100 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2023-11-17 11:10:16 +0100 |
| commit | d798fd5872e311fffeccd9930a45fdb2ed7eb2ea (patch) | |
| tree | e01cb6d76e8b3ee10c530bb85767b533fa1bebc3 /mullvad-cli/src | |
| parent | 0a82036e2b49dbd42819d36860b00289b3219a6b (diff) | |
| parent | b83735c6a569baecd2272d0a78721fe7998a47ce (diff) | |
| download | mullvadvpn-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.rs | 101 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/mod.rs | 1 | ||||
| -rw-r--r-- | mullvad-cli/src/main.rs | 7 |
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 } => { |
