diff options
| author | David Lönnhager <david.l@mullvad.net> | 2021-10-26 09:36:17 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2021-10-26 09:36:17 +0200 |
| commit | eaa0c1f96cb8f0d70426efe2a5733d37584babd9 (patch) | |
| tree | 585d21f873012d3429969ecd3998b61fa0b25fea | |
| parent | 6a99850fa3a5d3766dfad18da396194c7c73c33c (diff) | |
| parent | 937e4a630dcef076353892d147264da2c7e7d911 (diff) | |
| download | mullvadvpn-eaa0c1f96cb8f0d70426efe2a5733d37584babd9.tar.xz mullvadvpn-eaa0c1f96cb8f0d70426efe2a5733d37584babd9.zip | |
Merge branch 'move-settings'
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 7 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 5 | ||||
| -rw-r--r-- | mullvad-daemon/src/migrations/mod.rs | 302 | ||||
| -rw-r--r-- | mullvad-daemon/src/migrations/v1.rs (renamed from mullvad-types/src/settings/migrations/v1.rs) | 4 | ||||
| -rw-r--r-- | mullvad-daemon/src/migrations/v2.rs (renamed from mullvad-types/src/settings/migrations/v2.rs) | 3 | ||||
| -rw-r--r-- | mullvad-daemon/src/migrations/v3.rs (renamed from mullvad-types/src/settings/migrations/v3.rs) | 6 | ||||
| -rw-r--r-- | mullvad-daemon/src/migrations/v4.rs (renamed from mullvad-types/src/settings/migrations/v4.rs) | 7 | ||||
| -rw-r--r-- | mullvad-daemon/src/settings.rs | 355 | ||||
| -rw-r--r-- | mullvad-types/src/settings/migrations/mod.rs | 107 | ||||
| -rw-r--r-- | mullvad-types/src/settings/mod.rs | 125 |
10 files changed, 470 insertions, 451 deletions
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 43ec701c93..556483b50e 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -12,6 +12,7 @@ mod geoip; pub mod logging; #[cfg(not(target_os = "android"))] pub mod management_interface; +mod migrations; mod relays; #[cfg(not(target_os = "android"))] pub mod rpc_uniqueness_check; @@ -602,6 +603,12 @@ where ); + if let Err(error) = migrations::migrate_all(&settings_dir).await { + log::error!( + "{}", + error.display_chain_with_msg("Failed to migrate settings") + ); + } let mut settings = SettingsPersister::load(&settings_dir).await; if version::is_beta_version() { diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index cef8d42f78..fea356a01f 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -932,10 +932,13 @@ fn map_settings_error(error: settings::Error) -> Status { match error { settings::Error::DeleteError(..) | settings::Error::WriteError(..) + | settings::Error::ReadError(..) | settings::Error::SetPermissions(..) => { Status::new(Code::FailedPrecondition, error.to_string()) } - settings::Error::SerializeError(..) => Status::new(Code::Internal, error.to_string()), + settings::Error::SerializeError(..) | settings::Error::ParseError(..) => { + Status::new(Code::Internal, error.to_string()) + } } } diff --git a/mullvad-daemon/src/migrations/mod.rs b/mullvad-daemon/src/migrations/mod.rs new file mode 100644 index 0000000000..594ed21df8 --- /dev/null +++ b/mullvad-daemon/src/migrations/mod.rs @@ -0,0 +1,302 @@ +use mullvad_types::settings::{Settings, CURRENT_SETTINGS_VERSION}; +use std::path::Path; +use tokio::{ + fs, + io::{self, AsyncWriteExt}, +}; + +mod v1; +mod v2; +mod v3; +mod v4; + +const SETTINGS_FILE: &str = "settings.json"; + +#[derive(err_derive::Error, Debug)] +#[error(no_from)] +pub enum Error { + #[error(display = "Failed to read the settings")] + ReadError(#[error(source)] io::Error), + + #[error(display = "Malformed settings")] + ParseError(#[error(source)] serde_json::Error), + + #[error(display = "Unable to read any version of the settings")] + NoMatchingVersion, + + #[error(display = "Unable to serialize settings to JSON")] + SerializeError(#[error(source)] serde_json::Error), + + #[error(display = "Unable to write new settings")] + WriteError(#[error(source)] io::Error), + + #[cfg(windows)] + #[error(display = "Failed to restore Windows update backup")] + WinMigrationError(#[error(source)] windows::Error), +} + +pub type Result<T> = std::result::Result<T, Error>; + + +trait SettingsMigration { + fn version_matches(&self, settings: &mut serde_json::Value) -> bool; + fn migrate(&self, settings: &mut serde_json::Value) -> Result<()>; +} + +pub async fn migrate_all(settings_dir: &Path) -> Result<()> { + #[cfg(windows)] + windows::migrate_after_windows_update(settings_dir) + .await + .map_err(Error::WinMigrationError)?; + + let path = settings_dir.join(SETTINGS_FILE); + + if !path.is_file() { + return Ok(()); + } + + let settings_bytes = fs::read(&path).await.map_err(Error::ReadError)?; + + let mut settings: serde_json::Value = + serde_json::from_reader(&settings_bytes[..]).map_err(Error::ParseError)?; + + if !settings.is_object() { + return Err(Error::NoMatchingVersion); + } + + { + let settings: Settings = + serde_json::from_slice(&settings_bytes[..]).map_err(Error::ParseError)?; + if settings.get_settings_version() == CURRENT_SETTINGS_VERSION { + return Ok(()); + } + } + + let migrations: Vec<Box<dyn SettingsMigration>> = vec![ + Box::new(v1::Migration), + Box::new(v2::Migration), + Box::new(v3::Migration), + Box::new(v4::Migration), + ]; + + for migration in &migrations { + if !migration.version_matches(&mut settings) { + continue; + } + migration.migrate(&mut settings)?; + } + + let buffer = serde_json::to_string_pretty(&settings).map_err(Error::SerializeError)?; + + let mut options = fs::OpenOptions::new(); + #[cfg(unix)] + { + options.mode(0o600); + } + let mut file = options + .create(true) + .write(true) + .truncate(true) + .open(&path) + .await + .map_err(Error::WriteError)?; + file.write_all(&buffer.into_bytes()) + .await + .map_err(Error::WriteError)?; + + Ok(()) +} + +#[cfg(windows)] +mod windows { + use std::{ffi::OsStr, io, os::windows::ffi::OsStrExt, path::Path, ptr}; + use talpid_types::ErrorExt; + use tokio::fs; + use winapi::{ + shared::{minwindef::TRUE, winerror::ERROR_SUCCESS}, + um::{ + accctrl::{SE_FILE_OBJECT, SE_OBJECT_TYPE}, + aclapi::GetNamedSecurityInfoW, + securitybaseapi::IsWellKnownSid, + winbase::LocalFree, + winnt::{ + WinBuiltinAdministratorsSid, WinLocalSystemSid, OWNER_SECURITY_INFORMATION, PSID, + SECURITY_DESCRIPTOR, SECURITY_INFORMATION, SID, WELL_KNOWN_SID_TYPE, + }, + }, + }; + + const MIGRATION_DIRNAME: &str = "windows.old"; + const MIGRATE_FILES: [(&str, bool); 2] = + [("settings.json", true), ("account-history.json", false)]; + + #[derive(err_derive::Error, Debug)] + #[error(no_from)] + pub enum Error { + #[error(display = "Unable to find local appdata directory")] + FindAppData, + + #[error(display = "Could not acquire security descriptor of backup directory")] + SecurityInformation(#[error(source)] io::Error), + + #[error(display = "Backup directory is not owned by SYSTEM or Built-in Administrators")] + WrongOwner, + + #[error(display = "Failed to copy files during migration")] + IoError(#[error(source)] io::Error), + } + + /// Attempts to restore the Mullvad settings from `C:\windows.old` after an update of Windows. + /// Upon success, it returns `Ok(true)` if the migration succeeded, and `Ok(false)` if no + /// migration was needed. + pub async fn migrate_after_windows_update( + destination_settings_dir: &Path, + ) -> Result<bool, Error> { + let system_appdata_dir = dirs_next::data_local_dir().ok_or(Error::FindAppData)?; + if !destination_settings_dir.starts_with(system_appdata_dir) { + return Ok(false); + } + + let settings_path = destination_settings_dir.join(super::SETTINGS_FILE); + if settings_path.exists() { + return Ok(false); + } + + let mut components = destination_settings_dir.components(); + let prefix = if let Some(prefix) = components.next() { + prefix + } else { + return Ok(false); + }; + let root = if let Some(root) = components.next() { + root + } else { + return Ok(false); + }; + + let windows_old_dir = Path::new(&prefix).join(&root).join(MIGRATION_DIRNAME); + let source_settings_dir = Path::new(&windows_old_dir).join(&components); + if !source_settings_dir.exists() { + return Ok(false); + } + + let security_info = + SecurityInformation::from_file(windows_old_dir.as_path(), OWNER_SECURITY_INFORMATION) + .map_err(Error::SecurityInformation)?; + + let owner_sid = security_info.owner().ok_or(Error::WrongOwner)?; + + if !is_well_known_sid(owner_sid, WinLocalSystemSid) + && !is_well_known_sid(owner_sid, WinBuiltinAdministratorsSid) + { + return Err(Error::WrongOwner); + } + + if !destination_settings_dir.exists() { + fs::create_dir_all(destination_settings_dir) + .await + .map_err(Error::IoError)?; + } + + let mut result = Ok(true); + + for (file, required) in &MIGRATE_FILES { + let from = source_settings_dir.join(file); + let to = destination_settings_dir.join(file); + + log::debug!("Migrating {} to {}", from.display(), to.display()); + + match fs::copy(&from, &to).await { + Ok(_) => { + let _ = fs::remove_file(from).await; + } + Err(error) => { + log::error!( + "{}", + error.display_chain_with_msg(&format!( + "Failed to copy {} to {}", + from.display(), + to.display() + )) + ); + if *required { + result = Err(Error::IoError(error)); + } + } + } + } + + if let Err(error) = fs::remove_dir(source_settings_dir).await { + log::trace!( + "{}", + error.display_chain_with_msg("Failed to delete backup directory") + ); + } + + result + } + + struct SecurityInformation { + security_descriptor: *mut SECURITY_DESCRIPTOR, + owner: PSID, + } + + impl SecurityInformation { + pub fn from_file<T: AsRef<OsStr>>( + path: T, + security_information: SECURITY_INFORMATION, + ) -> Result<Self, io::Error> { + Self::from_object(path, SE_FILE_OBJECT, security_information) + } + + pub fn from_object<T: AsRef<OsStr>>( + object_name: T, + object_type: SE_OBJECT_TYPE, + security_information: SECURITY_INFORMATION, + ) -> Result<Self, io::Error> { + let mut u16_path: Vec<u16> = object_name.as_ref().encode_wide().collect(); + u16_path.push(0u16); + + let mut security_descriptor = ptr::null_mut(); + let mut owner = ptr::null_mut(); + + let status = unsafe { + GetNamedSecurityInfoW( + u16_path.as_ptr(), + object_type, + security_information, + &mut owner, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + &mut security_descriptor, + ) + }; + + if status != ERROR_SUCCESS { + return Err(std::io::Error::from_raw_os_error(status as i32)); + } + + Ok(SecurityInformation { + security_descriptor: security_descriptor as *mut _, + owner, + }) + } + + pub fn owner(&self) -> Option<&SID> { + unsafe { (self.owner as *const SID).as_ref() } + } + + // TODO: Can be expanded with `group()`, `dacl()`, and `sacl()`. + } + + impl Drop for SecurityInformation { + fn drop(&mut self) { + unsafe { LocalFree(self.security_descriptor as *mut _) }; + } + } + + fn is_well_known_sid(sid: &SID, well_known_sid_type: WELL_KNOWN_SID_TYPE) -> bool { + unsafe { IsWellKnownSid(sid as *const SID as *mut _, well_known_sid_type) == TRUE } + } +} diff --git a/mullvad-types/src/settings/migrations/v1.rs b/mullvad-daemon/src/migrations/v1.rs index 8e9ed04b89..a7c8e51738 100644 --- a/mullvad-types/src/settings/migrations/v1.rs +++ b/mullvad-daemon/src/migrations/v1.rs @@ -1,5 +1,5 @@ use super::Result; -use crate::relay_constraints::Constraint; +use mullvad_types::{relay_constraints::Constraint, settings::SettingsVersion}; use talpid_types::net::TunnelType; @@ -53,7 +53,7 @@ impl super::SettingsMigration for Migration { } settings["show_beta_releases"] = serde_json::json!(false); - settings["settings_version"] = serde_json::json!(super::SettingsVersion::V2); + settings["settings_version"] = serde_json::json!(SettingsVersion::V2); Ok(()) } diff --git a/mullvad-types/src/settings/migrations/v2.rs b/mullvad-daemon/src/migrations/v2.rs index 04d94eacbf..1ccf247212 100644 --- a/mullvad-types/src/settings/migrations/v2.rs +++ b/mullvad-daemon/src/migrations/v2.rs @@ -1,5 +1,6 @@ -use super::{Error, Result, SettingsVersion}; +use super::{Error, Result}; use crate::wireguard::{MAX_ROTATION_INTERVAL, MIN_ROTATION_INTERVAL}; +use mullvad_types::settings::SettingsVersion; use std::time::Duration; diff --git a/mullvad-types/src/settings/migrations/v3.rs b/mullvad-daemon/src/migrations/v3.rs index b7d9cb1f2e..042fd79dcd 100644 --- a/mullvad-types/src/settings/migrations/v3.rs +++ b/mullvad-daemon/src/migrations/v3.rs @@ -1,5 +1,7 @@ -use super::{Error, Result, SettingsVersion}; -use crate::settings::{CustomDnsOptions, DefaultDnsOptions, DnsOptions, DnsState}; +use super::{Error, Result}; +use mullvad_types::settings::{ + CustomDnsOptions, DefaultDnsOptions, DnsOptions, DnsState, SettingsVersion, +}; pub(super) struct Migration; diff --git a/mullvad-types/src/settings/migrations/v4.rs b/mullvad-daemon/src/migrations/v4.rs index f1b3bd43ab..7a459ce516 100644 --- a/mullvad-types/src/settings/migrations/v4.rs +++ b/mullvad-daemon/src/migrations/v4.rs @@ -1,5 +1,8 @@ -use super::{Error, Result, SettingsVersion}; -use crate::relay_constraints::{Constraint, TransportPort}; +use super::{Error, Result}; +use mullvad_types::{ + relay_constraints::{Constraint, TransportPort}, + settings::SettingsVersion, +}; use talpid_types::net::TransportProtocol; diff --git a/mullvad-daemon/src/settings.rs b/mullvad-daemon/src/settings.rs index e3cfeb8ddc..1715a92594 100644 --- a/mullvad-daemon/src/settings.rs +++ b/mullvad-daemon/src/settings.rs @@ -1,6 +1,5 @@ #[cfg(not(target_os = "android"))] use futures::TryFutureExt; -use log::{debug, error, info}; use mullvad_types::{ relay_constraints::{BridgeSettings, BridgeState, RelaySettingsUpdate}, settings::{DnsOptions, Settings}, @@ -23,7 +22,14 @@ const SETTINGS_FILE: &str = "settings.json"; #[derive(err_derive::Error, Debug)] +#[error(no_from)] pub enum Error { + #[error(display = "Unable to read settings file {}", _0)] + ReadError(String, #[error(source)] io::Error), + + #[error(display = "Unable to parse settings file")] + ParseError(#[error(source)] serde_json::Error), + #[error(display = "Unable to remove settings file {}", _0)] #[cfg(not(target_os = "android"))] DeleteError(String, #[error(source)] io::Error), @@ -38,22 +44,6 @@ pub enum Error { SetPermissions(#[error(source)] io::Error), } -#[derive(err_derive::Error, Debug)] -enum LoadSettingsError { - #[error(display = "Cannot find settings file")] - FileNotFound, - - #[error(display = "Unable to read settings file")] - Other(#[error(source)] io::Error), - - #[error(display = "Unable to parse settings file")] - ParseError(#[error(source)] mullvad_types::settings::Error), - - #[cfg(windows)] - #[error(display = "Failed to restore Windows update backup")] - WinMigrationError(#[error(source)] windows::Error), -} - #[derive(Debug)] pub struct SettingsPersister { @@ -62,10 +52,19 @@ pub struct SettingsPersister { } impl SettingsPersister { - /// Loads user settings from file. If no file is present it returns the defaults. + /// Loads user settings from file. If it fails, it returns the defaults. pub async fn load(settings_dir: &Path) -> Self { let path = settings_dir.join(SETTINGS_FILE); - let (mut settings, mut should_save) = Self::load_settings(&path).await; + let (mut settings, mut should_save) = match Self::load_from_file(&path).await { + Ok(value) => value, + Err(error) => { + log::warn!( + "{}", + error.display_chain_with_msg("Failed to load settings. Using defaults.") + ); + (Settings::default(), true) + } + }; // Force IPv6 to be enabled on Android if cfg!(target_os = "android") { @@ -77,7 +76,7 @@ impl SettingsPersister { if should_save { if let Err(error) = persister.save().await { - error!( + log::error!( "{}", error.display_chain_with_msg("Failed to save updated settings") ); @@ -87,63 +86,30 @@ impl SettingsPersister { persister } - async fn load_settings(path: &Path) -> (Settings, bool) { - let error = match Self::load_settings_from_file(path).await { - Ok(value) => return value, - Err(error) => error, - }; + async fn load_from_file(path: &Path) -> Result<(Settings, bool), Error> { + log::info!("Loading settings from {}", path.display()); - #[cfg(windows)] - let error = if let LoadSettingsError::FileNotFound = error { - info!( - "No settings file found. Attempting migration from Windows update backup location" - ); - match windows::migrate_after_windows_update() { - Ok(Some(())) => match Self::load_settings_from_file(path).await { - Ok(value) => return value, - Err(error) => error, - }, - Ok(None) => LoadSettingsError::FileNotFound, - Err(error) => LoadSettingsError::WinMigrationError(error), + let settings_bytes = match fs::read(path).await { + Ok(bytes) => bytes, + Err(error) => { + if error.kind() == io::ErrorKind::NotFound { + log::info!("No settings were found. Using defaults."); + return Ok((Settings::default(), true)); + } else { + return Err(Error::ReadError(path.display().to_string(), error)); + } } - } else { - error }; - - if let LoadSettingsError::FileNotFound = error { - info!("No settings were found. Using defaults."); - } else { - info!( - "{}", - error.display_chain_with_msg("Failed to load settings. Using defaults.") - ); - } - - (Settings::default(), true) + Ok((Self::load_from_bytes(&settings_bytes)?, false)) } - async fn load_settings_from_file(path: &Path) -> Result<(Settings, bool), LoadSettingsError> { - info!("Loading settings from {}", path.display()); - - let settings_bytes = fs::read(path).await.map_err(|error| { - if error.kind() == io::ErrorKind::NotFound { - LoadSettingsError::FileNotFound - } else { - LoadSettingsError::Other(error) - } - })?; - - Settings::load_from_bytes(&settings_bytes) - .map(|settings| (settings, false)) - .or_else(|_| { - Settings::migrate_from_bytes(&settings_bytes).map(|settings| (settings, true)) - }) - .map_err(LoadSettingsError::ParseError) + fn load_from_bytes(bytes: &[u8]) -> Result<Settings, Error> { + serde_json::from_slice(bytes).map_err(Error::ParseError) } /// Serializes the settings and saves them to the file it was loaded from. async fn save(&mut self) -> Result<(), Error> { - debug!("Writing settings to {}", self.path.display()); + log::debug!("Writing settings to {}", self.path.display()); let buffer = serde_json::to_string_pretty(&self.settings).map_err(Error::SerializeError)?; let mut options = fs::OpenOptions::new(); @@ -370,198 +336,85 @@ impl Deref for SettingsPersister { } } +#[cfg(test)] +mod test { + use super::SettingsPersister; + use mullvad_types::settings::SettingsVersion; + use serde_json; -#[cfg(windows)] -mod windows { - use std::{ffi::OsStr, fs, io, os::windows::ffi::OsStrExt, path::Path, ptr}; - use talpid_types::ErrorExt; - use winapi::{ - shared::{minwindef::TRUE, winerror::ERROR_SUCCESS}, - um::{ - accctrl::{SE_FILE_OBJECT, SE_OBJECT_TYPE}, - aclapi::GetNamedSecurityInfoW, - securitybaseapi::IsWellKnownSid, - winbase::LocalFree, - winnt::{ - WinBuiltinAdministratorsSid, WinLocalSystemSid, OWNER_SECURITY_INFORMATION, PSID, - SECURITY_DESCRIPTOR, SECURITY_INFORMATION, SID, WELL_KNOWN_SID_TYPE, - }, - }, - }; - - const MIGRATION_DIRNAME: &str = "windows.old"; - const MIGRATE_FILES: [(&str, bool); 2] = - [("settings.json", true), ("account-history.json", false)]; - - #[derive(err_derive::Error, Debug)] - #[error(no_from)] - pub enum Error { - #[error(display = "Unable to find settings directory")] - FindSettings(#[error(source)] mullvad_paths::Error), - - #[error(display = "Unable to find local appdata directory")] - FindAppData, - - #[error(display = "Could not acquire security descriptor of backup directory")] - SecurityInformation(#[error(source)] io::Error), - - #[error(display = "Backup directory is not owned by SYSTEM or Built-in Administrators")] - WrongOwner, - - #[error(display = "Failed to copy files during migration")] - IoError(#[error(source)] io::Error), + #[test] + #[should_panic] + fn test_deserialization_failure_version_too_small() { + let _version: SettingsVersion = serde_json::from_str("1").expect("Version too small"); } - /// Attempts to restore the Mullvad settings from `C:\windows.old` after an update of Windows. - /// Upon success, it returns `Ok(Some(()))` if the migration succeeded, and `Ok(None)` if no - /// migration was needed. - pub fn migrate_after_windows_update() -> Result<Option<()>, Error> { - let destination_settings_dir = - mullvad_paths::settings_dir().map_err(Error::FindSettings)?; - - let system_appdata_dir = dirs_next::data_local_dir().ok_or(Error::FindAppData)?; - if !destination_settings_dir.starts_with(system_appdata_dir) { - return Ok(None); - } - - let settings_path = destination_settings_dir.join(super::SETTINGS_FILE); - if settings_path.exists() { - return Ok(None); - } - - let mut components = destination_settings_dir.components(); - let prefix = if let Some(prefix) = components.next() { - prefix - } else { - return Ok(None); - }; - let root = if let Some(root) = components.next() { - root - } else { - return Ok(None); - }; - - let windows_old_dir = Path::new(&prefix).join(&root).join(MIGRATION_DIRNAME); - let source_settings_dir = Path::new(&windows_old_dir).join(&components); - if !source_settings_dir.exists() { - return Ok(None); - } - - let security_info = - SecurityInformation::from_file(windows_old_dir.as_path(), OWNER_SECURITY_INFORMATION) - .map_err(Error::SecurityInformation)?; - - let owner_sid = security_info.owner().ok_or(Error::WrongOwner)?; - - if !is_well_known_sid(owner_sid, WinLocalSystemSid) - && !is_well_known_sid(owner_sid, WinBuiltinAdministratorsSid) - { - return Err(Error::WrongOwner); - } - - if !destination_settings_dir.exists() { - fs::create_dir_all(&destination_settings_dir).map_err(Error::IoError)?; - } - - let mut result = Ok(Some(())); - - for (file, required) in &MIGRATE_FILES { - let from = source_settings_dir.join(file); - let to = destination_settings_dir.join(file); - - log::debug!("Migrating {} to {}", from.display(), to.display()); - - match fs::copy(&from, &to) { - Ok(_) => { - let _ = fs::remove_file(from); - } - Err(error) => { - log::error!( - "{}", - error.display_chain_with_msg(&format!( - "Failed to copy {} to {}", - from.display(), - to.display() - )) - ); - if *required { - result = Err(Error::IoError(error)); - } - } - } - } - - if let Err(error) = fs::remove_dir(source_settings_dir) { - log::trace!( - "{}", - error.display_chain_with_msg("Failed to delete backup directory") - ); - } - - result + #[test] + #[should_panic] + fn test_deserialization_failure_version_too_big() { + let _version: SettingsVersion = serde_json::from_str("1000").expect("Version too big"); } - struct SecurityInformation { - security_descriptor: *mut SECURITY_DESCRIPTOR, - owner: PSID, + #[test] + fn test_deserialization_success() { + let _version: SettingsVersion = + serde_json::from_str("2").expect("Failed to deserialize valid version"); } - impl SecurityInformation { - pub fn from_file<T: AsRef<OsStr>>( - path: T, - security_information: SECURITY_INFORMATION, - ) -> Result<Self, io::Error> { - Self::from_object(path, SE_FILE_OBJECT, security_information) - } - - pub fn from_object<T: AsRef<OsStr>>( - object_name: T, - object_type: SE_OBJECT_TYPE, - security_information: SECURITY_INFORMATION, - ) -> Result<Self, io::Error> { - let mut u16_path: Vec<u16> = object_name.as_ref().encode_wide().collect(); - u16_path.push(0u16); - - let mut security_descriptor = ptr::null_mut(); - let mut owner = ptr::null_mut(); - - let status = unsafe { - GetNamedSecurityInfoW( - u16_path.as_ptr(), - object_type, - security_information, - &mut owner, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - &mut security_descriptor, - ) - }; - - if status != ERROR_SUCCESS { - return Err(std::io::Error::from_raw_os_error(status as i32)); - } - - Ok(SecurityInformation { - security_descriptor: security_descriptor as *mut _, - owner, - }) - } - - pub fn owner(&self) -> Option<&SID> { - unsafe { (self.owner as *const SID).as_ref() } - } - - // TODO: Can be expanded with `group()`, `dacl()`, and `sacl()`. + #[test] + fn test_serialization_success() { + let version = SettingsVersion::V2; + let s = serde_json::to_string(&version).expect("Failed to serialize"); + assert_eq!(s, "2"); } - impl Drop for SecurityInformation { - fn drop(&mut self) { - unsafe { LocalFree(self.security_descriptor as *mut _) }; - } - } + #[test] + fn test_deserialization() { + let settings = br#"{ + "account_token": "0000000000000000", + "relay_settings": { + "normal": { + "location": { + "only": { + "country": "gb" + } + }, + "tunnel_protocol": { + "only": "wireguard" + }, + "wireguard_constraints": { + "port": "any" + }, + "openvpn_constraints": { + "port": "any", + "protocol": "any" + } + } + }, + "bridge_settings": { + "normal": { + "location": "any" + } + }, + "bridge_state": "auto", + "allow_lan": true, + "block_when_disconnected": false, + "auto_connect": true, + "tunnel_options": { + "openvpn": { + "mssfix": null + }, + "wireguard": { + "mtu": null, + "rotation_interval": null + }, + "generic": { + "enable_ipv6": true + } + }, + "settings_version": 5, + "show_beta_releases": false + }"#; - fn is_well_known_sid(sid: &SID, well_known_sid_type: WELL_KNOWN_SID_TYPE) -> bool { - unsafe { IsWellKnownSid(sid as *const SID as *mut _, well_known_sid_type) == TRUE } + let _ = SettingsPersister::load_from_bytes(settings).unwrap(); } } diff --git a/mullvad-types/src/settings/migrations/mod.rs b/mullvad-types/src/settings/migrations/mod.rs deleted file mode 100644 index b3a81feaa7..0000000000 --- a/mullvad-types/src/settings/migrations/mod.rs +++ /dev/null @@ -1,107 +0,0 @@ -use super::{Error, Result}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -mod v1; -mod v2; -mod v3; -mod v4; - - -#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] -#[repr(u32)] -pub enum SettingsVersion { - V2 = 2, - V3 = 3, - V4 = 4, - V5 = 5, -} - -pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V5; - -impl<'de> Deserialize<'de> for SettingsVersion { - fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> - where - D: Deserializer<'de>, - { - match <u32>::deserialize(deserializer)? { - v if v == SettingsVersion::V2 as u32 => Ok(SettingsVersion::V2), - v if v == SettingsVersion::V3 as u32 => Ok(SettingsVersion::V3), - v if v == SettingsVersion::V4 as u32 => Ok(SettingsVersion::V4), - v if v == SettingsVersion::V5 as u32 => Ok(SettingsVersion::V5), - v => Err(serde::de::Error::custom(format!( - "{} is not a valid SettingsVersion", - v - ))), - } - } -} - -impl Serialize for SettingsVersion { - fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> - where - S: Serializer, - { - serializer.serialize_u32(*self as u32) - } -} - - -trait SettingsMigration { - fn version_matches(&self, settings: &mut serde_json::Value) -> bool; - fn migrate(&self, settings: &mut serde_json::Value) -> Result<()>; -} - -pub fn try_migrate_settings(mut settings_file: &[u8]) -> Result<crate::settings::Settings> { - let mut settings: serde_json::Value = - serde_json::from_reader(&mut settings_file).map_err(Error::ParseError)?; - - if !settings.is_object() { - return Err(Error::NoMatchingVersion); - } - - let migrations: Vec<Box<dyn SettingsMigration>> = vec![ - Box::new(v1::Migration), - Box::new(v2::Migration), - Box::new(v3::Migration), - Box::new(v4::Migration), - ]; - - for migration in &migrations { - if !migration.version_matches(&mut settings) { - continue; - } - migration.migrate(&mut settings)?; - } - - serde_json::from_value(settings).map_err(Error::ParseError) -} - -#[cfg(test)] -mod test { - use super::SettingsVersion; - use serde_json; - - #[test] - #[should_panic] - fn test_deserialization_failure_version_too_small() { - let _version: SettingsVersion = serde_json::from_str("1").expect("Version too small"); - } - - #[test] - #[should_panic] - fn test_deserialization_failure_version_too_big() { - let _version: SettingsVersion = serde_json::from_str("1000").expect("Version too big"); - } - - #[test] - fn test_deserialization_success() { - let _version: SettingsVersion = - serde_json::from_str("2").expect("Failed to deserialize valid version"); - } - - #[test] - fn test_serialization_success() { - let version = SettingsVersion::V2; - let s = serde_json::to_string(&version).expect("Failed to serialize"); - assert_eq!(s, "2"); - } -} diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index 7567b3e050..67e4891883 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -8,28 +8,48 @@ use crate::{ #[cfg(target_os = "android")] use jnix::{jni::objects::JObject, FromJava, IntoJava, JnixEnv}; use log::{debug, info}; -use serde::{Deserialize, Serialize}; -use serde_json; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::net::IpAddr; #[cfg(target_os = "windows")] use std::{collections::HashSet, path::PathBuf}; use talpid_types::net::{self, openvpn, GenericTunnelOptions}; -mod migrations; +pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V5; -pub type Result<T> = std::result::Result<T, Error>; - -#[derive(err_derive::Error, Debug)] -#[error(no_from)] -pub enum Error { - #[error(display = "Malformed settings")] - ParseError(#[error(source)] serde_json::Error), +#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] +#[repr(u32)] +pub enum SettingsVersion { + V2 = 2, + V3 = 3, + V4 = 4, + V5 = 5, +} - #[error(display = "Settings version mismatch")] - VersionMismatch, +impl<'de> Deserialize<'de> for SettingsVersion { + fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> + where + D: Deserializer<'de>, + { + match <u32>::deserialize(deserializer)? { + v if v == SettingsVersion::V2 as u32 => Ok(SettingsVersion::V2), + v if v == SettingsVersion::V3 as u32 => Ok(SettingsVersion::V3), + v if v == SettingsVersion::V4 as u32 => Ok(SettingsVersion::V4), + v if v == SettingsVersion::V5 as u32 => Ok(SettingsVersion::V5), + v => Err(serde::de::Error::custom(format!( + "{} is not a valid SettingsVersion", + v + ))), + } + } +} - #[error(display = "Unable to read any version of the settings")] - NoMatchingVersion, +impl Serialize for SettingsVersion { + fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_u32(*self as u32) + } } @@ -65,7 +85,7 @@ pub struct Settings { pub split_tunnel: SplitTunnelSettings, /// Specifies settings schema version #[cfg_attr(target_os = "android", jnix(skip))] - settings_version: migrations::SettingsVersion, + settings_version: SettingsVersion, } #[cfg(windows)] @@ -95,24 +115,12 @@ impl Default for Settings { show_beta_releases: false, #[cfg(windows)] split_tunnel: SplitTunnelSettings::default(), - settings_version: migrations::CURRENT_SETTINGS_VERSION, + settings_version: CURRENT_SETTINGS_VERSION, } } } impl Settings { - pub fn load_from_bytes(bytes: &[u8]) -> Result<Self> { - let settings: Self = serde_json::from_slice(bytes).map_err(Error::ParseError)?; - if settings.settings_version < migrations::CURRENT_SETTINGS_VERSION { - return Err(Error::VersionMismatch); - } - Ok(settings) - } - - pub fn migrate_from_bytes(bytes: &[u8]) -> Result<Self> { - migrations::try_migrate_settings(&bytes) - } - pub fn get_account_token(&self) -> Option<String> { self.account_token.clone() } @@ -190,6 +198,10 @@ impl Settings { false } } + + pub fn get_settings_version(&self) -> SettingsVersion { + self.settings_version + } } /// TunnelOptions holds configuration data that applies to all kinds of tunnels. @@ -306,60 +318,3 @@ impl Default for TunnelOptions { } } } - - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_deserialization() { - let settings = br#"{ - "account_token": "0000000000000000", - "relay_settings": { - "normal": { - "location": { - "only": { - "country": "gb" - } - }, - "tunnel_protocol": { - "only": "wireguard" - }, - "wireguard_constraints": { - "port": "any" - }, - "openvpn_constraints": { - "port": "any", - "protocol": "any" - } - } - }, - "bridge_settings": { - "normal": { - "location": "any" - } - }, - "bridge_state": "auto", - "allow_lan": true, - "block_when_disconnected": false, - "auto_connect": true, - "tunnel_options": { - "openvpn": { - "mssfix": null - }, - "wireguard": { - "mtu": null, - "rotation_interval": null - }, - "generic": { - "enable_ipv6": true - } - }, - "settings_version": 5, - "show_beta_releases": false - }"#; - - let _ = Settings::load_from_bytes(settings).unwrap(); - } -} |
