summaryrefslogtreecommitdiffhomepage
path: root/mullvad-cli/src
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-01-10 13:06:03 +0100
committerDavid Lönnhager <david.l@mullvad.net>2024-01-10 13:06:03 +0100
commit75eb89c820f12d488a76934f59ba29fe999cf59c (patch)
treedee82ab3f307f8d1a72a0cf01fceb8fa51612ac1 /mullvad-cli/src
parentedbd1f52c44ba6ee9a290146f714453fc87e689d (diff)
parent01bcf9ed16a903aa80ff8bc48cc6f2aaf3d6e80d (diff)
downloadmullvadvpn-75eb89c820f12d488a76934f59ba29fe999cf59c.tar.xz
mullvadvpn-75eb89c820f12d488a76934f59ba29fe999cf59c.zip
Merge branch 'add-settings-json-export'
Diffstat (limited to 'mullvad-cli/src')
-rw-r--r--mullvad-cli/src/cmds/import_settings.rs101
-rw-r--r--mullvad-cli/src/cmds/mod.rs2
-rw-r--r--mullvad-cli/src/cmds/patch.rs60
-rw-r--r--mullvad-cli/src/main.rs13
4 files changed, 72 insertions, 104 deletions
diff --git a/mullvad-cli/src/cmds/import_settings.rs b/mullvad-cli/src/cmds/import_settings.rs
deleted file mode 100644
index a89c6814c3..0000000000
--- a/mullvad-cli/src/cmds/import_settings.rs
+++ /dev/null
@@ -1,101 +0,0 @@
-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 29e0508d80..5a9cede691 100644
--- a/mullvad-cli/src/cmds/mod.rs
+++ b/mullvad-cli/src/cmds/mod.rs
@@ -9,10 +9,10 @@ pub mod bridge;
pub mod custom_list;
pub mod debug;
pub mod dns;
-pub mod import_settings;
pub mod lan;
pub mod lockdown;
pub mod obfuscation;
+pub mod patch;
pub mod proxies;
pub mod relay;
pub mod relay_constraints;
diff --git a/mullvad-cli/src/cmds/patch.rs b/mullvad-cli/src/cmds/patch.rs
new file mode 100644
index 0000000000..bec686e56d
--- /dev/null
+++ b/mullvad-cli/src/cmds/patch.rs
@@ -0,0 +1,60 @@
+use anyhow::{Context, Result};
+use mullvad_management_interface::MullvadProxyClient;
+use std::{
+ fs::File,
+ io::{stdin, BufReader, Read},
+};
+
+/// 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 import(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(())
+}
+
+/// If source is specified, write a patch to the file. Otherwise, write the patch to standard
+/// output.
+pub async fn export(dest: String) -> Result<()> {
+ let mut rpc = MullvadProxyClient::new().await?;
+ let blob = rpc
+ .export_json_settings()
+ .await
+ .context("Error exporting patch")?;
+
+ match dest.as_str() {
+ "-" => {
+ println!("{blob}");
+ Ok(())
+ }
+ _ => tokio::fs::write(&dest, blob)
+ .await
+ .context(format!("Failed to write to path {dest}")),
+ }
+}
+
+fn get_blob(source: String) -> Result<String> {
+ match source.as_str() {
+ "-" => {
+ read_settings_from_reader(BufReader::new(stdin())).context("Failed to read from stdin")
+ }
+ _ => read_settings_from_reader(File::open(&source)?)
+ .context(format!("Failed to read from path: {source}")),
+ }
+}
+
+/// Read until EOF or until newline when the last pair of braces has been closed
+fn read_settings_from_reader(mut reader: impl Read) -> Result<String> {
+ let mut s = String::new();
+ reader.read_to_string(&mut s)?;
+ Ok(s)
+}
diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs
index 270d3c293e..c0a8a8b992 100644
--- a/mullvad-cli/src/main.rs
+++ b/mullvad-cli/src/main.rs
@@ -138,11 +138,19 @@ enum Cli {
#[clap(subcommand)]
CustomList(custom_list::CustomList),
- /// Apply a JSON patch
+ /// Apply a JSON patch generated by 'export-settings'
+ #[clap(arg_required_else_help = true)]
ImportSettings {
/// File to read from. If this is "-", read from standard input
file: String,
},
+
+ /// Export a JSON patch based on the current settings
+ #[clap(arg_required_else_help = true)]
+ ExportSettings {
+ /// File to write to. If this is "-", write to standard output
+ file: String,
+ },
}
#[tokio::main]
@@ -169,7 +177,8 @@ 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,
+ Cli::ImportSettings { file } => patch::import(file).await,
+ Cli::ExportSettings { file } => patch::export(file).await,
#[cfg(all(unix, not(target_os = "android")))]
Cli::ShellCompletions { shell, dir } => {