From 410484d284c42c1af5a8f06107e82338d21c506a Mon Sep 17 00:00:00 2001 From: Sebastian Streich Date: Wed, 15 Jan 2025 14:24:34 +0100 Subject: [PATCH 1/7] Fix the command handling of NM --- extension/bridge/src/commands.rs | 37 ++++++++ extension/bridge/src/io.rs | 127 ++++++++++++++++++++++++++ extension/bridge/src/main.rs | 150 +++++-------------------------- 3 files changed, 185 insertions(+), 129 deletions(-) create mode 100644 extension/bridge/src/commands.rs create mode 100644 extension/bridge/src/io.rs diff --git a/extension/bridge/src/commands.rs b/extension/bridge/src/commands.rs new file mode 100644 index 0000000000..9a369c743e --- /dev/null +++ b/extension/bridge/src/commands.rs @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + + use std::error::Error; + use serde_json::{Value, json}; + + /** + * Handles commands that are sent from + * [Extension] === > [NativeMessagingBridge] + * + * Returns true if the command was handled, in which case it should + * *not* be forwarded to the VPN Client. + * + * Will attempt to print to STDOUT in case a command needs a response. + * + */ + pub fn handle(val:&Value)-> Result>{ + let obj = val.as_object().ok_or("Not an object")?; + // Type of command is in {t:'doThing'} + let cmd = obj.get_key_value("t").ok_or("Missing obj.t")?; + + match cmd.1.as_str().ok_or("T is not a string")? { + "bridge_pong" =>{ + crate::io::write_output(std::io::stdout(),&json!({"status": "bridge_pong"})) + .expect("Unable to Write to STDOUT?"); + return Ok(true); + } + _ =>{ + // We did not handle this. + return Ok(false); + } + } + } + \ No newline at end of file diff --git a/extension/bridge/src/io.rs b/extension/bridge/src/io.rs new file mode 100644 index 0000000000..c5112da3ac --- /dev/null +++ b/extension/bridge/src/io.rs @@ -0,0 +1,127 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; + use mio::net::TcpStream; + use mio::{Events, Interest, Poll, Token, Waker}; + use serde_json::{json, Value}; + use std::io::{Cursor, Read, Write}; + use std::mem::size_of; + use std::sync::mpsc::channel; + use std::sync::Arc; + use std::{thread, time}; + use std::env; + + + +#[derive(PartialEq)] +enum ReaderState { + ReadingLength, + ReadingBuffer, +} + +pub struct Reader { + state: ReaderState, + buffer: Vec, + length: usize, +} + +impl Reader { + pub fn new() -> Reader { + Reader { + state: ReaderState::ReadingLength, + buffer: Vec::new(), + length: 0, + } + } + + pub fn read_input(&mut self, mut input: R) -> Option { + // Until we are able to read things from the stream... + loop { + if self.state == ReaderState::ReadingLength { + assert!(self.buffer.len() < size_of::()); + + let mut buffer = vec![0; size_of::() - self.buffer.len()]; + match input.read(&mut buffer) { + Ok(size) => { + // Maybe we have read just part of the buffer. Let's append + // only what we have been read. + buffer.truncate(size); + self.buffer.append(&mut buffer); + + // Not enough data yet. + if self.buffer.len() < size_of::() { + continue; + } + + // Let's convert our buffer into a u32. + let mut rdr = Cursor::new(&self.buffer); + self.length = rdr.read_u32::().unwrap() as usize; + if self.length == 0 { + continue; + } + + self.state = ReaderState::ReadingBuffer; + self.buffer = Vec::with_capacity(self.length); + } + _ => return None, + } + } + + if self.state == ReaderState::ReadingBuffer { + assert!(self.length > 0); + assert!(self.buffer.len() < self.length); + + let mut buffer = vec![0; self.length - self.buffer.len()]; + match input.read(&mut buffer) { + Ok(size) => { + // Maybe we have read just part of the buffer. Let's append + // only what we have been read. + buffer.truncate(size); + self.buffer.append(&mut buffer); + + // Not enough data yet. + if self.buffer.len() < self.length { + continue; + } + + match serde_json::from_slice(&self.buffer) { + Ok(value) => { + self.buffer.clear(); + self.state = ReaderState::ReadingLength; + return Some(value); + } + _ => { + self.buffer.clear(); + self.state = ReaderState::ReadingLength; + continue; + } + } + } + _ => return None, + } + } + } + } +} + +pub fn write_output(mut output: W, value: &Value) -> Result<(), std::io::Error> { + let msg = serde_json::to_string(value)?; + let len = msg.len(); + output.write_u32::(len as u32)?; + output.write_all(msg.as_bytes())?; + output.flush()?; + Ok(()) +} + +pub fn write_vpn_down(error: bool) { + let field = if error { "error" } else { "status" }; + let value = json!({field: "vpn-client-down"}); + write_output(std::io::stdout(), &value).expect("Unable to write to STDOUT"); +} + +pub fn write_vpn_up() { + let value = json!({"status": "vpn-client-up"}); + write_output(std::io::stdout(), &value).expect("Unable to write to STDOUT"); +} diff --git a/extension/bridge/src/main.rs b/extension/bridge/src/main.rs index 9af3cfdb1a..6d31b9f43c 100644 --- a/extension/bridge/src/main.rs +++ b/extension/bridge/src/main.rs @@ -13,6 +13,9 @@ use std::sync::Arc; use std::{thread, time}; use std::env; +mod commands; +mod io; + const SERVER_AND_PORT: &str = "127.0.0.1:8754"; const ALLOW_LISTED_WEBEXTENSIONS: [&str;2] = [ @@ -20,116 +23,6 @@ const ALLOW_LISTED_WEBEXTENSIONS: [&str;2] = [ "vpn@mozilla.com" ]; -#[derive(PartialEq)] -enum ReaderState { - ReadingLength, - ReadingBuffer, -} - -pub struct Reader { - state: ReaderState, - buffer: Vec, - length: usize, -} - -impl Reader { - pub fn new() -> Reader { - Reader { - state: ReaderState::ReadingLength, - buffer: Vec::new(), - length: 0, - } - } - - pub fn read_input(&mut self, mut input: R) -> Option { - // Until we are able to read things from the stream... - loop { - if self.state == ReaderState::ReadingLength { - assert!(self.buffer.len() < size_of::()); - - let mut buffer = vec![0; size_of::() - self.buffer.len()]; - match input.read(&mut buffer) { - Ok(size) => { - // Maybe we have read just part of the buffer. Let's append - // only what we have been read. - buffer.truncate(size); - self.buffer.append(&mut buffer); - - // Not enough data yet. - if self.buffer.len() < size_of::() { - continue; - } - - // Let's convert our buffer into a u32. - let mut rdr = Cursor::new(&self.buffer); - self.length = rdr.read_u32::().unwrap() as usize; - if self.length == 0 { - continue; - } - - self.state = ReaderState::ReadingBuffer; - self.buffer = Vec::with_capacity(self.length); - } - _ => return None, - } - } - - if self.state == ReaderState::ReadingBuffer { - assert!(self.length > 0); - assert!(self.buffer.len() < self.length); - - let mut buffer = vec![0; self.length - self.buffer.len()]; - match input.read(&mut buffer) { - Ok(size) => { - // Maybe we have read just part of the buffer. Let's append - // only what we have been read. - buffer.truncate(size); - self.buffer.append(&mut buffer); - - // Not enough data yet. - if self.buffer.len() < self.length { - continue; - } - - match serde_json::from_slice(&self.buffer) { - Ok(value) => { - self.buffer.clear(); - self.state = ReaderState::ReadingLength; - return Some(value); - } - _ => { - self.buffer.clear(); - self.state = ReaderState::ReadingLength; - continue; - } - } - } - _ => return None, - } - } - } - } -} - -fn write_output(mut output: W, value: &Value) -> Result<(), std::io::Error> { - let msg = serde_json::to_string(value)?; - let len = msg.len(); - output.write_u32::(len as u32)?; - output.write_all(msg.as_bytes())?; - output.flush()?; - Ok(()) -} - -fn write_vpn_down(error: bool) { - let field = if error { "error" } else { "status" }; - let value = json!({field: "vpn-client-down"}); - write_output(std::io::stdout(), &value).expect("Unable to write to STDOUT"); -} - -fn write_vpn_up() { - let value = json!({"status": "vpn-client-up"}); - write_output(std::io::stdout(), &value).expect("Unable to write to STDOUT"); -} fn main() { /* @@ -168,24 +61,23 @@ fn main() { let waker = Arc::new(Waker::new(poll.registry(), STDIN_WAKER).unwrap()); let waker_cloned = waker.clone(); thread::spawn(move || { - let mut r = Reader::new(); + let mut r = io::Reader::new(); loop { match r.read_input(std::io::stdin()) { Some(value) => { - if value == "bridge_ping" { - // A simple ping/pong message. - let pong_value = json!("bridge_pong"); - write_output(std::io::stdout(), &pong_value) - .expect("Unable to write to STDOUT"); - } else { - // For a real message, we wake up the main thread. - sender - .send(value) - .expect("Unable to send data to the main thread"); - waker_cloned.wake().expect("Unable to wake the main thread"); + match commands::handle(&value){ + Ok(true) =>{ + // Command was handled successfully. + } + _ =>{ + // For a real message, we wake up the main thread. + sender + .send(value) + .expect("Unable to send data to the main thread"); + waker_cloned.wake().expect("Unable to wake the main thread"); + } } } - None => { thread::sleep(time::Duration::from_millis(500)); } @@ -204,7 +96,7 @@ fn main() { .register(&mut stream, VPN, Interest::READABLE | Interest::WRITABLE) .unwrap(); - let mut r = Reader::new(); + let mut r = io::Reader::new(); // This second loop processes messages coming from the tcp stream and from the // STDIN thread via the waker/sender. @@ -221,7 +113,7 @@ fn main() { { poll.registry().deregister(&mut stream).unwrap(); vpn_connected = false; - write_vpn_down(false); + io::write_vpn_down(false); thread::sleep(time::Duration::from_millis(500)); new_connection_needed = true; } @@ -233,7 +125,7 @@ fn main() { if !vpn_connected && (event.is_readable() || event.is_writable()) { vpn_connected = true; - write_vpn_up(); + io::write_vpn_up(); } if event.is_readable() { @@ -241,7 +133,7 @@ fn main() { loop { match r.read_input(&mut stream) { Some(value) => { - write_output(std::io::stdout(), &value) + io::write_output(std::io::stdout(), &value) .expect("Unable to write to STDOUT"); } _ => { @@ -253,8 +145,8 @@ fn main() { } STDIN_WAKER => { let value = receiver.recv().unwrap(); - if let Err(_) = write_output(&mut stream, &value) { - write_vpn_down(true); + if let Err(_) = io::write_output(&mut stream, &value) { + io::write_vpn_down(true); } } _ => unreachable!(), From 6dc8179aa08b77d74399662c2ef95527de7eaac6 Mon Sep 17 00:00:00 2001 From: Sebastian Streich Date: Thu, 16 Jan 2025 16:04:28 +0100 Subject: [PATCH 2/7] Fix tests --- extension/bridge/src/commands.rs | 2 +- tests/nativemessaging/testbridge.cpp | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/extension/bridge/src/commands.rs b/extension/bridge/src/commands.rs index 9a369c743e..28e9ce355a 100644 --- a/extension/bridge/src/commands.rs +++ b/extension/bridge/src/commands.rs @@ -23,7 +23,7 @@ let cmd = obj.get_key_value("t").ok_or("Missing obj.t")?; match cmd.1.as_str().ok_or("T is not a string")? { - "bridge_pong" =>{ + "bridge_ping" =>{ crate::io::write_output(std::io::stdout(),&json!({"status": "bridge_pong"})) .expect("Unable to Write to STDOUT?"); return Ok(true); diff --git a/tests/nativemessaging/testbridge.cpp b/tests/nativemessaging/testbridge.cpp index d4e23bd188..f62d82a070 100644 --- a/tests/nativemessaging/testbridge.cpp +++ b/tests/nativemessaging/testbridge.cpp @@ -5,13 +5,16 @@ #include "testbridge.h" #include "helperserver.h" +#include void TestBridge::bridge_ping() { QVERIFY(s_nativeMessagingProcess); + // A simple ping/pong. - QVERIFY(write("\"bridge_ping\"")); - QCOMPARE(readIgnoringStatus(), "\"bridge_pong\""); + QVERIFY(write(R"({"t": "bridge_ping"})")); + auto const json = QJsonDocument::fromJson(readIgnoringStatus()); + QCOMPARE(json["status"].toString(),"bridge_pong"); } void TestBridge::app_ping_failure() { From 6f95fc792d51ab3d06cc8cd23935283bc6ce0ed0 Mon Sep 17 00:00:00 2001 From: Sebastian Streich Date: Mon, 20 Jan 2025 13:56:20 +0100 Subject: [PATCH 3/7] Add the command to start --- extension/bridge/src/commands.rs | 42 +++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/extension/bridge/src/commands.rs b/extension/bridge/src/commands.rs index 9a369c743e..f501fdcb41 100644 --- a/extension/bridge/src/commands.rs +++ b/extension/bridge/src/commands.rs @@ -28,10 +28,50 @@ .expect("Unable to Write to STDOUT?"); return Ok(true); } + "start" =>{ + let out = launcher::start_vpn(); + crate::io::write_output(std::io::stdout(),&out) + .expect("Unable to Write to STDOUT?"); + return Ok(true); + } _ =>{ // We did not handle this. return Ok(false); } } } - \ No newline at end of file + + +#[cfg(target_os = "windows")] +mod launcher { + const CLIENT_PATH: &str = "C:\\Program Files\\Mozilla\\Mozilla VPN\\Mozilla VPN.exe"; + + use std::os::windows::process::CommandExt; + use std::process::Command; + + use serde_json::json; + + const CREATE_NEW_PROCESS_GROUP: u32 = 0x200; // CREATE_NEW_PROCESS_GROUP + const DETACHED_PROCESS: u32 = 0x00000008; // DETACHED_PROCESS + + pub fn start_vpn() -> serde_json::Value{ + let result = Command::new(CLIENT_PATH) + .args(["-foreground"]) + .creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS) + .spawn(); + + match result { + Ok(_) => json!("{status:'requested_start'}"), + Err(_) => json!("{error:'start_failed'}"), + } + } + + +} + +#[cfg(not(target_os = "windows"))] +mod launcher { + pub fn start_vpn() -> serde_json::Value{ + json!("{error:'start_unsupported!'}") + } +} From a8d8b21eb41ec2cd7e8baaf3417c409c6fd135a4 Mon Sep 17 00:00:00 2001 From: Sebastian Streich Date: Thu, 23 Jan 2025 12:47:12 +0100 Subject: [PATCH 4/7] Thanks, clippy! --- extension/bridge/src/commands.rs | 6 +++--- extension/bridge/src/io.rs | 12 ++++++------ extension/bridge/src/main.rs | 4 ---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/extension/bridge/src/commands.rs b/extension/bridge/src/commands.rs index 7a285350be..92fafff7af 100644 --- a/extension/bridge/src/commands.rs +++ b/extension/bridge/src/commands.rs @@ -26,17 +26,17 @@ "bridge_ping" =>{ crate::io::write_output(std::io::stdout(),&json!({"status": "bridge_pong"})) .expect("Unable to Write to STDOUT?"); - return Ok(true); + Ok(true) } "start" =>{ let out = launcher::start_vpn(); crate::io::write_output(std::io::stdout(),&out) .expect("Unable to Write to STDOUT?"); - return Ok(true); + Ok(true) } _ =>{ // We did not handle this. - return Ok(false); + Ok(false) } } } diff --git a/extension/bridge/src/io.rs b/extension/bridge/src/io.rs index c5112da3ac..c40ada709a 100644 --- a/extension/bridge/src/io.rs +++ b/extension/bridge/src/io.rs @@ -3,15 +3,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; - use mio::net::TcpStream; - use mio::{Events, Interest, Poll, Token, Waker}; + + use serde_json::{json, Value}; use std::io::{Cursor, Read, Write}; use std::mem::size_of; - use std::sync::mpsc::channel; - use std::sync::Arc; - use std::{thread, time}; - use std::env; + + + + diff --git a/extension/bridge/src/main.rs b/extension/bridge/src/main.rs index 6d31b9f43c..da427358a3 100644 --- a/extension/bridge/src/main.rs +++ b/extension/bridge/src/main.rs @@ -2,12 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; use mio::net::TcpStream; use mio::{Events, Interest, Poll, Token, Waker}; -use serde_json::{json, Value}; -use std::io::{Cursor, Read, Write}; -use std::mem::size_of; use std::sync::mpsc::channel; use std::sync::Arc; use std::{thread, time}; From cda6a7e9caabae5cb5bfceb4fe9c74969445b90d Mon Sep 17 00:00:00 2001 From: Sebastian Streich Date: Thu, 23 Jan 2025 13:41:27 +0100 Subject: [PATCH 5/7] fix non windows --- extension/bridge/src/commands.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/extension/bridge/src/commands.rs b/extension/bridge/src/commands.rs index 92fafff7af..a6d0260f8d 100644 --- a/extension/bridge/src/commands.rs +++ b/extension/bridge/src/commands.rs @@ -71,6 +71,7 @@ mod launcher { #[cfg(not(target_os = "windows"))] mod launcher { + use serde_json::json; pub fn start_vpn() -> serde_json::Value{ json!("{error:'start_unsupported!'}") } From 329f5ec60bd09b7a34c5480d705c481abe164ce2 Mon Sep 17 00:00:00 2001 From: Sebastian Streich Date: Thu, 23 Jan 2025 13:46:18 +0100 Subject: [PATCH 6/7] formatting --- tests/nativemessaging/testbridge.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/nativemessaging/testbridge.cpp b/tests/nativemessaging/testbridge.cpp index f62d82a070..73b39931e0 100644 --- a/tests/nativemessaging/testbridge.cpp +++ b/tests/nativemessaging/testbridge.cpp @@ -4,17 +4,17 @@ #include "testbridge.h" -#include "helperserver.h" #include +#include "helperserver.h" + void TestBridge::bridge_ping() { QVERIFY(s_nativeMessagingProcess); - // A simple ping/pong. QVERIFY(write(R"({"t": "bridge_ping"})")); auto const json = QJsonDocument::fromJson(readIgnoringStatus()); - QCOMPARE(json["status"].toString(),"bridge_pong"); + QCOMPARE(json["status"].toString(), "bridge_pong"); } void TestBridge::app_ping_failure() { From d32334fa20edf65f7a7a013ecb265af105e107e9 Mon Sep 17 00:00:00 2001 From: Sebastian Streich Date: Thu, 23 Jan 2025 14:58:05 +0100 Subject: [PATCH 7/7] casing --- tests/nativemessaging/testbridge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/nativemessaging/testbridge.cpp b/tests/nativemessaging/testbridge.cpp index 73b39931e0..b44f1f1973 100644 --- a/tests/nativemessaging/testbridge.cpp +++ b/tests/nativemessaging/testbridge.cpp @@ -4,7 +4,7 @@ #include "testbridge.h" -#include +#include #include "helperserver.h"