summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--mullvad-daemon/src/lib.rs7
-rw-r--r--mullvad-daemon/src/management_interface.rs5
-rw-r--r--mullvad-daemon/src/migrations/mod.rs302
-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.rs355
-rw-r--r--mullvad-types/src/settings/migrations/mod.rs107
-rw-r--r--mullvad-types/src/settings/mod.rs125
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();
- }
-}