diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2021-01-07 10:36:44 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2021-01-07 10:36:44 +0100 |
| commit | 9fb0385ef16d6a76ffd6fe7be0d6267e43d0ac51 (patch) | |
| tree | 48c7d3e78c9b452534af071d30203d7cf9dbf980 | |
| parent | 9cfad3027adea59fa26e4c610d5a0ad41e5743ed (diff) | |
| parent | f3c7e627f41fae59302de936c2ae8212300fbf1e (diff) | |
| download | mullvadvpn-9fb0385ef16d6a76ffd6fe7be0d6267e43d0ac51.tar.xz mullvadvpn-9fb0385ef16d6a76ffd6fe7be0d6267e43d0ac51.zip | |
Merge branch 'version-check-platform-header'
| -rw-r--r-- | CHANGELOG.md | 3 | ||||
| -rw-r--r-- | Cargo.lock | 10 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-daemon/Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-daemon/src/version_check.rs | 10 | ||||
| -rw-r--r-- | mullvad-platform-metadata/Cargo.toml | 12 | ||||
| -rw-r--r-- | mullvad-platform-metadata/src/android.rs | 35 | ||||
| -rw-r--r-- | mullvad-platform-metadata/src/command.rs | 10 | ||||
| -rw-r--r-- | mullvad-platform-metadata/src/lib.rs | 17 | ||||
| -rw-r--r-- | mullvad-platform-metadata/src/linux.rs | 62 | ||||
| -rw-r--r-- | mullvad-platform-metadata/src/macos.rs | 18 | ||||
| -rw-r--r-- | mullvad-platform-metadata/src/windows.rs | 54 | ||||
| -rw-r--r-- | mullvad-problem-report/Cargo.toml | 5 | ||||
| -rw-r--r-- | mullvad-problem-report/src/metadata.rs | 155 | ||||
| -rw-r--r-- | mullvad-rpc/src/lib.rs | 22 | ||||
| -rw-r--r-- | mullvad-rpc/src/rest.rs | 7 |
16 files changed, 254 insertions, 168 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 2858f85f99..b72ba378c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ Line wrap the file at 100 chars. Th ## [Unreleased] ### Added +- Add header containing OS version to version-check API call to enable OS specific compatibility and + vulnerability checks. + #### Android - Allow to configure the tunnel to use custom DNS servers. diff --git a/Cargo.lock b/Cargo.lock index b7c07e91e2..cc29caeba4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1243,6 +1243,7 @@ dependencies = [ "log-panics", "mullvad-management-interface", "mullvad-paths", + "mullvad-platform-metadata", "mullvad-rpc", "mullvad-types", "nix 0.19.0", @@ -1322,6 +1323,13 @@ dependencies = [ ] [[package]] +name = "mullvad-platform-metadata" +version = "0.1.0" +dependencies = [ + "rs-release", +] + +[[package]] name = "mullvad-problem-report" version = "2020.8.0-beta2" dependencies = [ @@ -1332,9 +1340,9 @@ dependencies = [ "err-derive", "lazy_static", "mullvad-paths", + "mullvad-platform-metadata", "mullvad-rpc", "regex", - "rs-release", "talpid-types", "tokio", "uuid", diff --git a/Cargo.toml b/Cargo.toml index f87705cf22..4d155d203a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "mullvad-problem-report", "mullvad-jni", "mullvad-paths", + "mullvad-platform-metadata", "mullvad-types", "mullvad-rpc", # "mullvad-tests", diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index f26ed548c6..9698ab67cf 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -27,6 +27,7 @@ tokio = { version = "0.2", features = [ "fs", "rt-threaded", "stream", "sync" ] uuid = { version = "0.8", features = ["v4"] } mullvad-paths = { path = "../mullvad-paths" } +mullvad-platform-metadata = { path = "../mullvad-platform-metadata" } mullvad-types = { path = "../mullvad-types" } mullvad-rpc = { path = "../mullvad-rpc" } talpid-core = { path = "../talpid-core" } diff --git a/mullvad-daemon/src/version_check.rs b/mullvad-daemon/src/version_check.rs index d0895671ba..8f52f5694a 100644 --- a/mullvad-daemon/src/version_check.rs +++ b/mullvad-daemon/src/version_check.rs @@ -86,6 +86,7 @@ pub(crate) struct VersionUpdater { cache_path: PathBuf, update_sender: DaemonEventSender<AppVersionInfo>, last_app_version_info: AppVersionInfo, + platform_version: String, next_update_time: Instant, show_beta_releases: bool, rx: Option<mpsc::Receiver<bool>>, @@ -116,6 +117,7 @@ impl VersionUpdater { let version_proxy = AppVersionProxy::new(rpc_handle); let cache_path = cache_dir.join(VERSION_INFO_FILENAME); let (tx, rx) = mpsc::channel(1); + let platform_version = mullvad_platform_metadata::short_version(); ( Self { @@ -123,6 +125,7 @@ impl VersionUpdater { cache_path, update_sender, last_app_version_info, + platform_version, next_update_time: Instant::now(), show_beta_releases, rx: Some(rx), @@ -135,8 +138,13 @@ impl VersionUpdater { &self, ) -> impl Future<Output = Result<mullvad_rpc::AppVersionResponse, Error>> + Send + 'static { let version_proxy = self.version_proxy.clone(); + let platform_version = self.platform_version.clone(); let download_future_factory = move || { - let response = version_proxy.version_check(PRODUCT_VERSION.to_owned(), PLATFORM); + let response = version_proxy.version_check( + PRODUCT_VERSION.to_owned(), + PLATFORM, + platform_version.clone(), + ); response.map_err(Error::Download) }; diff --git a/mullvad-platform-metadata/Cargo.toml b/mullvad-platform-metadata/Cargo.toml new file mode 100644 index 0000000000..d1ba2f6a14 --- /dev/null +++ b/mullvad-platform-metadata/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "mullvad-platform-metadata" +version = "0.1.0" +authors = ["Mullvad VPN"] +description = "Platform metadata detection functions" +license = "GPL-3.0" +edition = "2018" +publish = false + + +[target.'cfg(target_os = "linux")'.dependencies] +rs-release = "0.1.7" diff --git a/mullvad-platform-metadata/src/android.rs b/mullvad-platform-metadata/src/android.rs new file mode 100644 index 0000000000..5573586808 --- /dev/null +++ b/mullvad-platform-metadata/src/android.rs @@ -0,0 +1,35 @@ +use std::collections::HashMap; + +mod command; +use command::command_stdout_lossy; + +pub fn version() -> String { + let version = get_prop("ro.build.version.release").unwrap_or_else(|| "N/A".to_owned()); + let api_level = get_prop("ro.build.version.sdk").unwrap_or_else(|| "N/A".to_owned()); + + let manufacturer = + get_prop("ro.product.manufacturer").unwrap_or_else(|| "Unknown brand".to_owned()); + let product = get_prop("ro.product.model").unwrap_or_else(|| "Unknown model".to_owned()); + + format!( + "Android {} (API: {}) - {} {}", + version, api_level, manufacturer, product + ) +} + +pub fn short_version() -> String { + version() +} + +pub fn extra_metadata() -> HashMap<String, String> { + let mut metadata = HashMap::new(); + metadata.insert( + "abi".to_owned(), + get_prop("ro.product.cpu.abilist").unwrap_or_else(|| "N/A".to_owned()), + ); + metadata +} + +fn get_prop(property: &str) -> Option<String> { + command_stdout_lossy("getprop", &[property]) +} diff --git a/mullvad-platform-metadata/src/command.rs b/mullvad-platform-metadata/src/command.rs new file mode 100644 index 0000000000..87ed6c40a0 --- /dev/null +++ b/mullvad-platform-metadata/src/command.rs @@ -0,0 +1,10 @@ +use std::process::Command; + +/// Helper for getting stdout of some command as a String. Ignores the exit code of the command. +pub fn command_stdout_lossy(cmd: &str, args: &[&str]) -> Option<String> { + Command::new(cmd) + .args(args) + .output() + .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string()) + .ok() +} diff --git a/mullvad-platform-metadata/src/lib.rs b/mullvad-platform-metadata/src/lib.rs new file mode 100644 index 0000000000..f4ba78eaeb --- /dev/null +++ b/mullvad-platform-metadata/src/lib.rs @@ -0,0 +1,17 @@ +#[cfg(target_os = "linux")] +#[path = "linux.rs"] +mod imp; + +#[cfg(target_os = "macos")] +#[path = "macos.rs"] +mod imp; + +#[cfg(windows)] +#[path = "windows.rs"] +mod imp; + +#[cfg(target_os = "android")] +#[path = "android.rs"] +mod imp; + +pub use self::imp::{extra_metadata, short_version, version}; diff --git a/mullvad-platform-metadata/src/linux.rs b/mullvad-platform-metadata/src/linux.rs new file mode 100644 index 0000000000..484d2eaaf8 --- /dev/null +++ b/mullvad-platform-metadata/src/linux.rs @@ -0,0 +1,62 @@ +mod command; +use command::command_stdout_lossy; + +pub fn version() -> String { + // The OS version information is obtained first from the os-release file. If that + // information is incomplete or unavailable, an attempt is made to obtain the + // version information from the lsb_release command. If that fails, any partial + // information from os-release is used if available, or a fallback message if + // reading from the os-release file produced + // no version information. + let version = read_os_release_file().unwrap_or_else(|incomplete_info| { + parse_lsb_release().unwrap_or_else(|| { + incomplete_info.unwrap_or_else(|| String::from("[Failed to detect version]")) + }) + }); + + format!("Linux {}", version) +} + +pub fn short_version() -> String { + version() +} + +fn read_os_release_file() -> Result<String, Option<String>> { + let mut os_release_info = rs_release::get_os_release().map_err(|_| None)?; + let os_name = os_release_info.remove("NAME"); + let os_version = os_release_info.remove("VERSION"); + + if os_name.is_some() || os_version.is_some() { + let full_info_available = os_name.is_some() && os_version.is_some(); + + let gathered_info = format!( + "{} {}", + os_name.unwrap_or_else(|| "[unknown distribution]".to_owned()), + os_version.unwrap_or_else(|| "[unknown version]".to_owned()) + ); + + if full_info_available { + Ok(gathered_info) + } else { + // Partial version information + Err(Some(gathered_info)) + } + } else { + // No information was obtained + Err(None) + } +} + +fn parse_lsb_release() -> Option<String> { + command_stdout_lossy("lsb_release", &["-ds"]).and_then(|output| { + if output.is_empty() { + None + } else { + Some(output) + } + }) +} + +pub fn extra_metadata() -> impl Iterator<Item = (String, String)> { + std::iter::empty() +} diff --git a/mullvad-platform-metadata/src/macos.rs b/mullvad-platform-metadata/src/macos.rs new file mode 100644 index 0000000000..9ee496adb1 --- /dev/null +++ b/mullvad-platform-metadata/src/macos.rs @@ -0,0 +1,18 @@ +mod command; +use command::command_stdout_lossy; + +pub fn version() -> String { + format!( + "macOS {}", + command_stdout_lossy("sw_vers", &["-productVersion"]) + .unwrap_or(String::from("[Failed to detect version]")) + ) +} + +pub fn short_version() -> String { + version() +} + +pub fn extra_metadata() -> impl Iterator<Item = (String, String)> { + std::iter::empty() +} diff --git a/mullvad-platform-metadata/src/windows.rs b/mullvad-platform-metadata/src/windows.rs new file mode 100644 index 0000000000..41faf0099b --- /dev/null +++ b/mullvad-platform-metadata/src/windows.rs @@ -0,0 +1,54 @@ +use std::collections::HashMap; + +mod command; +use command::command_stdout_lossy; + +pub fn version() -> String { + let system_info = system_info(); + let os_name = system_info.get("OS Name"); + let os_version = system_info.get("OS Version"); + let version = os_name.map(parse_version).unwrap_or(String::from("N/A")); + let full_version = os_version + .map(parse_full_version) + .unwrap_or(String::from("N/A")); + format!("Windows {} ({})", version, full_version) +} + +pub fn short_version() -> String { + let system_info = system_info(); + let os_name = system_info.get("OS Name"); + let version = os_name.map(parse_version).unwrap_or(String::from("N/A")); + format!("Windows {}", version) +} + +fn system_info() -> HashMap<String, String> { + let system_info = + command_stdout_lossy("systeminfo", &["/FO", "LIST"]).unwrap_or_else(String::new); + + let mut info_map = HashMap::new(); + system_info.lines().for_each(|line| { + let mut split = line.split(":"); + if let Some(key) = split.next() { + if let Some(value) = split.next() { + info_map.insert(key.to_owned(), value.to_owned()); + } + } + }); + + info_map +} + +fn parse_version(os_name: &String) -> String { + os_name + .trim() + .trim_start_matches("Microsoft Windows ") + .to_owned() +} + +fn parse_full_version(os_version: &String) -> String { + os_version.trim().to_owned() +} + +pub fn extra_metadata() -> impl Iterator<Item = (String, String)> { + std::iter::empty() +} diff --git a/mullvad-problem-report/Cargo.toml b/mullvad-problem-report/Cargo.toml index 79ae9b757d..18a749650f 100644 --- a/mullvad-problem-report/Cargo.toml +++ b/mullvad-problem-report/Cargo.toml @@ -18,6 +18,7 @@ uuid = { version = "0.8", features = ["v4"] } tokio = { version = "0.2", features = [ "rt-core" ] } mullvad-paths = { path = "../mullvad-paths" } +mullvad-platform-metadata = { path = "../mullvad-platform-metadata" } mullvad-rpc = { path = "../mullvad-rpc" } talpid-types = { path = "../talpid-types" } @@ -26,10 +27,6 @@ talpid-types = { path = "../talpid-types" } duct = "0.13" -[target.'cfg(target_os = "linux")'.dependencies] -rs-release = "0.1.7" - - [target.'cfg(windows)'.build-dependencies] winres = "0.1" winapi = "0.3" diff --git a/mullvad-problem-report/src/metadata.rs b/mullvad-problem-report/src/metadata.rs index 1e4d6c864d..0d7eecfb47 100644 --- a/mullvad-problem-report/src/metadata.rs +++ b/mullvad-problem-report/src/metadata.rs @@ -1,5 +1,4 @@ -use std::{collections::BTreeMap, process::Command}; - +use std::collections::BTreeMap; pub const PRODUCT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/product-version.txt")); @@ -10,155 +9,7 @@ pub fn collect() -> BTreeMap<String, String> { "mullvad-product-version".to_owned(), PRODUCT_VERSION.to_owned(), ); - metadata.insert("os".to_owned(), os::version()); - metadata.extend(os::extra_metadata()); + metadata.insert("os".to_owned(), mullvad_platform_metadata::version()); + metadata.extend(mullvad_platform_metadata::extra_metadata()); metadata } - -#[cfg(target_os = "linux")] -mod os { - pub fn version() -> String { - // The OS version information is obtained first from the os-release file. If that - // information is incomplete or unavailable, an attempt is made to obtain the - // version information from the lsb_release command. If that fails, any partial - // information from os-release is used if available, or a fallback message if - // reading from the os-release file produced - // no version information. - let version = read_os_release_file().unwrap_or_else(|incomplete_info| { - parse_lsb_release().unwrap_or_else(|| { - incomplete_info.unwrap_or_else(|| String::from("[Failed to detect version]")) - }) - }); - - format!("Linux {}", version) - } - - fn read_os_release_file() -> Result<String, Option<String>> { - let mut os_release_info = rs_release::get_os_release().map_err(|_| None)?; - let os_name = os_release_info.remove("NAME"); - let os_version = os_release_info.remove("VERSION"); - - if os_name.is_some() || os_version.is_some() { - let full_info_available = os_name.is_some() && os_version.is_some(); - - let gathered_info = format!( - "{} {}", - os_name.unwrap_or_else(|| "[unknown distribution]".to_owned()), - os_version.unwrap_or_else(|| "[unknown version]".to_owned()) - ); - - if full_info_available { - Ok(gathered_info) - } else { - // Partial version information - Err(Some(gathered_info)) - } - } else { - // No information was obtained - Err(None) - } - } - - fn parse_lsb_release() -> Option<String> { - super::command_stdout_lossy("lsb_release", &["-ds"]).and_then(|output| { - if output.is_empty() { - None - } else { - Some(output) - } - }) - } - - pub fn extra_metadata() -> impl Iterator<Item = (String, String)> { - std::iter::empty() - } -} - -#[cfg(target_os = "macos")] -mod os { - pub fn version() -> String { - format!( - "macOS {}", - super::command_stdout_lossy("sw_vers", &["-productVersion"]) - .unwrap_or(String::from("[Failed to detect version]")) - ) - } - - pub fn extra_metadata() -> impl Iterator<Item = (String, String)> { - std::iter::empty() - } -} - -#[cfg(windows)] -mod os { - pub fn version() -> String { - let system_info = - super::command_stdout_lossy("systeminfo", &["/FO", "LIST"]).unwrap_or_else(String::new); - - let mut version = None; - let mut full_version = None; - - for info_line in system_info.lines() { - let mut info_parts = info_line.split(":"); - - match info_parts.next() { - Some("OS Name") => { - version = info_parts - .next() - .map(|s| s.trim().trim_start_matches("Microsoft Windows ")) - } - Some("OS Version") => full_version = info_parts.next().map(str::trim), - _ => {} - } - } - - let version = version.unwrap_or("N/A"); - let full_version = full_version.unwrap_or("N/A"); - format!("Windows {} ({})", version, full_version) - } - - pub fn extra_metadata() -> impl Iterator<Item = (String, String)> { - std::iter::empty() - } -} - -#[cfg(target_os = "android")] -mod os { - use std::collections::HashMap; - - pub fn version() -> String { - let version = get_prop("ro.build.version.release").unwrap_or_else(|| "N/A".to_owned()); - let api_level = get_prop("ro.build.version.sdk").unwrap_or_else(|| "N/A".to_owned()); - - let manufacturer = - get_prop("ro.product.manufacturer").unwrap_or_else(|| "Unknown brand".to_owned()); - let product = get_prop("ro.product.model").unwrap_or_else(|| "Unknown model".to_owned()); - - format!( - "Android {} (API: {}) - {} {}", - version, api_level, manufacturer, product - ) - } - - pub fn extra_metadata() -> HashMap<String, String> { - let mut metadata = HashMap::new(); - metadata.insert( - "abi".to_owned(), - get_prop("ro.product.cpu.abilist").unwrap_or_else(|| "N/A".to_owned()), - ); - metadata - } - - fn get_prop(property: &str) -> Option<String> { - super::command_stdout_lossy("getprop", &[property]) - } -} - -/// Helper for getting stdout of some command as a String. Ignores the exit code of the command. -fn command_stdout_lossy(cmd: &str, args: &[&str]) -> Option<String> { - Command::new(cmd) - .args(args) - .output() - .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string()) - .ok() -} diff --git a/mullvad-rpc/src/lib.rs b/mullvad-rpc/src/lib.rs index 4beefa7d3b..e211a89c2e 100644 --- a/mullvad-rpc/src/lib.rs +++ b/mullvad-rpc/src/lib.rs @@ -343,21 +343,23 @@ impl AppVersionProxy { pub fn version_check( &self, - version: AppVersion, + app_version: AppVersion, platform: &str, + platform_version: String, ) -> impl Future<Output = Result<AppVersionResponse, rest::Error>> { let service = self.handle.service.clone(); - let request = rest::send_request( - &self.handle.factory, - service, - &format!("/v1/releases/{}/{}", platform, version), - Method::GET, - None, - StatusCode::OK, - ); + let path = format!("/v1/releases/{}/{}", platform, app_version); + let request = self.handle.factory.request(&path, Method::GET); - async move { rest::deserialize_body(request.await?).await } + async move { + let mut request = request?; + request.add_header("M-Platform-Version", platform_version)?; + + let response = service.request(request).await?; + let parsed_response = rest::parse_rest_response(response, StatusCode::OK).await?; + rest::deserialize_body(parsed_response).await + } } } diff --git a/mullvad-rpc/src/rest.rs b/mullvad-rpc/src/rest.rs index 12bc10263e..82a6271c74 100644 --- a/mullvad-rpc/src/rest.rs +++ b/mullvad-rpc/src/rest.rs @@ -319,6 +319,13 @@ impl RestRequest { self.timeout } + pub fn add_header(&mut self, key: &'static str, value: String) -> Result<()> { + let header_value = + http::HeaderValue::from_str(&value).map_err(Error::InvalidHeaderError)?; + self.request.headers_mut().insert(key, header_value); + Ok(()) + } + /// Converts into a `hyper::Request<hyper::Body>` fn into_request(self) -> Request { let Self { |
