diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2020-04-22 11:04:00 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2020-04-22 11:04:00 -0300 |
| commit | b8b36573ed691c704044dff9f22774337a44879d (patch) | |
| tree | af6ce3d69f484426cfd4ac2cee2150c871b1036b | |
| parent | 106937e21d66b19a9a291e7014b9f81a412e9933 (diff) | |
| parent | b18ab26965d4b7cff95e950995bec53ebff5c47e (diff) | |
| download | mullvadvpn-b8b36573ed691c704044dff9f22774337a44879d.tar.xz mullvadvpn-b8b36573ed691c704044dff9f22774337a44879d.zip | |
Merge branch 'dynamic-app-path'
21 files changed, 483 insertions, 382 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c69a42001..318deb91bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ Line wrap the file at 100 chars. Th #### Android - Change button colors on problem report no email confirmation dialog to match the desktop version. +- Fix crash when attempting to run app from the non-default location, such as the SD card or from a + different user profile. ### Security #### macOS diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt index 40e93ff882..66a55503d6 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt @@ -7,9 +7,11 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async -const val PROBLEM_REPORT_PATH = "/data/data/net.mullvad.mullvadvpn/problem_report.txt" +const val PROBLEM_REPORT_FILE = "problem_report.txt" + +class MullvadProblemReport(val logDirectory: File) { + private val problemReportPath = File(logDirectory, PROBLEM_REPORT_FILE) -class MullvadProblemReport { private var collectJob: Deferred<Boolean>? = null private var sendJob: Deferred<Boolean>? = null @@ -38,7 +40,7 @@ class MullvadProblemReport { if (!isActive) { collectJob = GlobalScope.async(Dispatchers.Default) { deleteReportFile() - collectReport(PROBLEM_REPORT_PATH) + collectReport(logDirectory.absolutePath, problemReportPath.absolutePath) } } } @@ -51,7 +53,11 @@ class MullvadProblemReport { if (currentJob == null || currentJob.isCompleted) { currentJob = GlobalScope.async(Dispatchers.Default) { val result = (collectJob?.await() ?: false) && - sendProblemReport(userEmail, userMessage, PROBLEM_REPORT_PATH) + sendProblemReport( + userEmail, + userMessage, + problemReportPath.absolutePath + ) if (result) { deleteReportFile() @@ -68,10 +74,10 @@ class MullvadProblemReport { } fun deleteReportFile() { - File(PROBLEM_REPORT_PATH).delete() + problemReportPath.delete() } - private external fun collectReport(reportPath: String): Boolean + private external fun collectReport(logDirectory: String, reportPath: String): Boolean private external fun sendProblemReport( userEmail: String, userMessage: String, diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileMigrator.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileMigrator.kt new file mode 100644 index 0000000000..cd325d8a6f --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileMigrator.kt @@ -0,0 +1,18 @@ +package net.mullvad.mullvadvpn.service + +import android.util.Log +import java.io.File + +class FileMigrator(val oldDirectory: File, val newDirectory: File) { + fun migrate(fileName: String) { + try { + val oldPath = File(oldDirectory, fileName) + + if (oldPath.exists()) { + oldPath.renameTo(File(newDirectory, fileName)) + } + } catch (exception: Exception) { + Log.w("mullvad", "Failed to migrate $fileName from $oldDirectory to $newDirectory") + } + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt index aac6175ec6..1ab65fa850 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt @@ -4,14 +4,16 @@ import android.content.Context import java.io.File import java.io.FileOutputStream -class FileResourceExtractor(val asset: String, val destination: String) { - fun extract(context: Context) { - if (!File(destination).exists()) { - extractFile(context) +class FileResourceExtractor(val context: Context) { + fun extract(asset: String) { + val destination = File(context.filesDir, asset) + + if (!destination.exists()) { + extractFile(asset, destination) } } - private fun extractFile(context: Context) { + private fun extractFile(asset: String, destination: File) { val destinationStream = FileOutputStream(destination) context diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt index 60dce6ff10..9c34ecb5d7 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt @@ -24,7 +24,7 @@ class MullvadDaemon(val vpnService: MullvadVpnService) { init { System.loadLibrary("mullvad_jni") - initialize(vpnService) + initialize(vpnService, vpnService.cacheDir.absolutePath, vpnService.filesDir.absolutePath) onSettingsChange.notify(getSettings()) } @@ -113,7 +113,11 @@ class MullvadDaemon(val vpnService: MullvadVpnService) { return verifyWireguardKey(daemonInterfaceAddress) } - private external fun initialize(vpnService: MullvadVpnService) + private external fun initialize( + vpnService: MullvadVpnService, + cacheDirectory: String, + resourceDirectory: String + ) private external fun deinitialize() private external fun connect(daemonInterfaceAddress: Long) diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt index 4df3ff6791..aba89e9e8a 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt @@ -4,6 +4,7 @@ import android.content.Intent import android.net.VpnService import android.os.Binder import android.os.IBinder +import java.io.File import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job @@ -14,10 +15,7 @@ import net.mullvad.talpid.TalpidVpnService import net.mullvad.talpid.util.EventNotifier private const val API_ROOT_CA_FILE = "api_root_ca.pem" -private const val API_ROOT_CA_PATH = "/data/data/net.mullvad.mullvadvpn/api_root_ca.pem" - private const val RELAYS_FILE = "relays.json" -private const val RELAYS_PATH = "/data/data/net.mullvad.mullvadvpn/relays.json" class MullvadVpnService : TalpidVpnService() { private enum class PendingAction { @@ -144,11 +142,7 @@ class MullvadVpnService : TalpidVpnService() { } private fun startDaemon() = GlobalScope.launch(Dispatchers.Default) { - FileResourceExtractor(API_ROOT_CA_FILE, API_ROOT_CA_PATH) - .extract(application) - - FileResourceExtractor(RELAYS_FILE, RELAYS_PATH) - .extract(application) + prepareFiles() val newDaemon = MullvadDaemon(this@MullvadVpnService).apply { onSettingsChange.subscribe { settings -> @@ -191,6 +185,23 @@ class MullvadVpnService : TalpidVpnService() { )) } + private fun prepareFiles() { + FileMigrator(File("/data/data/net.mullvad.mullvadvpn"), filesDir).apply { + migrate(API_ROOT_CA_FILE) + migrate(RELAYS_FILE) + migrate("settings.json") + migrate("daemon.log") + migrate("daemon.old.log") + migrate("wireguard.log") + migrate("wireguard.old.log") + } + + FileResourceExtractor(this).apply { + extract(API_ROOT_CA_FILE) + extract(RELAYS_FILE) + } + } + private fun stop() { isStopping = true stopDaemon() diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt index 93b85c001b..524d4ab00d 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt @@ -19,9 +19,10 @@ class MainActivity : FragmentActivity() { val KEY_SHOULD_CONNECT = "should_connect" } - val problemReport = MullvadProblemReport() val serviceNotifier = EventNotifier<ServiceConnection?>(null) + lateinit var problemReport: MullvadProblemReport + private var service: MullvadVpnService.LocalBinder? = null private var serviceConnection: ServiceConnection? = null private var serviceConnectionSubscription: Int? = null @@ -64,6 +65,9 @@ class MainActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + problemReport = MullvadProblemReport(filesDir) + setContentView(R.layout.main) if (savedInstanceState == null) { diff --git a/mullvad-cli/src/cmds/auto_connect.rs b/mullvad-cli/src/cmds/auto_connect.rs index bad4c5c1e6..385a2de7b6 100644 --- a/mullvad-cli/src/cmds/auto_connect.rs +++ b/mullvad-cli/src/cmds/auto_connect.rs @@ -49,7 +49,7 @@ impl AutoConnect { fn get(&self) -> Result<()> { let mut rpc = new_rpc_client()?; - let auto_connect = rpc.get_settings()?.get_auto_connect(); + let auto_connect = rpc.get_settings()?.auto_connect; println!("Autoconnect: {}", if auto_connect { "on" } else { "off" }); Ok(()) } diff --git a/mullvad-cli/src/cmds/beta_program.rs b/mullvad-cli/src/cmds/beta_program.rs index c63027ccfd..602de81db0 100644 --- a/mullvad-cli/src/cmds/beta_program.rs +++ b/mullvad-cli/src/cmds/beta_program.rs @@ -29,7 +29,7 @@ impl Command for BetaProgram { ("get", Some(_)) => { let mut rpc = new_rpc_client()?; let settings = rpc.get_settings()?; - let enabled_str = if settings.get_show_beta_releases().unwrap_or(false) { + let enabled_str = if settings.show_beta_releases.unwrap_or(false) { "on" } else { "off" diff --git a/mullvad-cli/src/cmds/block_when_disconnected.rs b/mullvad-cli/src/cmds/block_when_disconnected.rs index 9d9081a650..cd585720dd 100644 --- a/mullvad-cli/src/cmds/block_when_disconnected.rs +++ b/mullvad-cli/src/cmds/block_when_disconnected.rs @@ -49,7 +49,7 @@ impl BlockWhenDisconnected { fn get(&self) -> Result<()> { let mut rpc = new_rpc_client()?; - let block_when_disconnected = rpc.get_settings()?.get_block_when_disconnected(); + let block_when_disconnected = rpc.get_settings()?.block_when_disconnected; println!( "Network traffic will be {} when the VPN is disconnected", if block_when_disconnected { diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs index d0538e0d7c..0e5c339744 100644 --- a/mullvad-cli/src/cmds/bridge.rs +++ b/mullvad-cli/src/cmds/bridge.rs @@ -162,7 +162,7 @@ impl Bridge { let mut rpc = new_rpc_client()?; let settings = rpc.get_settings()?; println!("Bridge state - {}", settings.get_bridge_state()); - match settings.get_bridge_settings() { + match settings.bridge_settings { BridgeSettings::Custom(proxy) => { match proxy { openvpn::ProxySettings::Local(local_proxy) => { diff --git a/mullvad-cli/src/cmds/lan.rs b/mullvad-cli/src/cmds/lan.rs index 3a29bdd226..15d30cde50 100644 --- a/mullvad-cli/src/cmds/lan.rs +++ b/mullvad-cli/src/cmds/lan.rs @@ -49,7 +49,7 @@ impl Lan { fn get(&self) -> Result<()> { let mut rpc = new_rpc_client()?; - let allow_lan = rpc.get_settings()?.get_allow_lan(); + let allow_lan = rpc.get_settings()?.allow_lan; println!( "Local network sharing setting: {}", if allow_lan { "allow" } else { "block" } diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index 8b995cdc6b..5349174d4c 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -258,7 +258,7 @@ impl Tunnel { fn get_tunnel_options() -> Result<TunnelOptions> { let mut rpc = new_rpc_client()?; - Ok(rpc.get_settings()?.get_tunnel_options().clone()) + Ok(rpc.get_settings()?.tunnel_options) } fn process_openvpn_mssfix_unset() -> Result<()> { diff --git a/mullvad-cli/src/cmds/version.rs b/mullvad-cli/src/cmds/version.rs index 2acde8f76f..99df7b6d19 100644 --- a/mullvad-cli/src/cmds/version.rs +++ b/mullvad-cli/src/cmds/version.rs @@ -20,7 +20,7 @@ impl Command for Version { println!("\tIs supported: {}", version_info.current_is_supported); let settings = rpc.get_settings()?; - let is_updated = if settings.get_show_beta_releases().unwrap_or(false) { + let is_updated = if settings.show_beta_releases.unwrap_or(false) { version_info.latest == current_version } else { version_info.latest_stable == current_version diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 79cb4d192d..6dd6812dd2 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -37,11 +37,12 @@ use mullvad_types::{ RelaySettingsUpdate, }, relay_list::{Relay, RelayList}, + settings::Settings, states::{TargetState, TunnelState}, version::{AppVersion, AppVersionInfo}, wireguard::KeygenEvent, }; -use settings::Settings; +use settings::SettingsPersister; #[cfg(not(target_os = "android"))] use std::path::Path; use std::{ @@ -436,7 +437,7 @@ pub struct Daemon<L: EventListener> { tx: DaemonEventSender, reconnection_loop_tx: Option<mpsc::Sender<()>>, event_listener: L, - settings: Settings, + settings: SettingsPersister, account_history: account_history::AccountHistory, wg_key_proxy: WireguardKeyProxy<HttpHandle>, accounts_proxy: AccountsProxy<HttpHandle>, @@ -460,6 +461,7 @@ where pub fn start( log_dir: Option<PathBuf>, resource_dir: PathBuf, + settings_dir: PathBuf, cache_dir: PathBuf, event_listener: L, command_channel: DaemonCommandChannel, @@ -506,9 +508,9 @@ where ); tokio_remote.spawn(|_| version_check_future); - let mut settings = settings::load(); + let mut settings = SettingsPersister::load(&settings_dir); - if version::is_beta_version() && settings.get_show_beta_releases().is_none() { + if version::is_beta_version() && settings.show_beta_releases.is_none() { let _ = settings.set_show_beta_releases(true); } @@ -543,8 +545,8 @@ where tx: internal_event_tx.clone(), }; let tunnel_command_tx = tunnel_state_machine::spawn( - settings.get_allow_lan(), - settings.get_block_when_disconnected(), + settings.allow_lan, + settings.block_when_disconnected, tunnel_parameters_generator, log_dir, resource_dir, @@ -566,7 +568,7 @@ where relay_selector.update(); let initial_target_state = if settings.get_account_token().is_some() { - if settings.get_auto_connect() { + if settings.auto_connect { // Note: Auto-connect overrides the cached target state info!("Automatically connecting since auto-connect is turned on"); TargetState::Secured @@ -611,7 +613,7 @@ where token, daemon .settings - .get_tunnel_options() + .tunnel_options .wireguard .automatic_rotation .map(|hours| Duration::from_secs(60u64 * 60u64 * hours as u64)), @@ -757,7 +759,7 @@ where self.last_generated_relay = None; custom_relay // TODO(emilsp): generate proxy settings for custom tunnels - .to_tunnel_parameters(self.settings.get_tunnel_options().clone(), None) + .to_tunnel_parameters(self.settings.tunnel_options.clone(), None) .map_err(|e| { log::error!("Failed to resolve hostname for custom tunnel config: {}", e); ParameterGenerationError::CustomTunnelHostResultionError @@ -819,12 +821,12 @@ where account_token: String, retry_attempt: u32, ) -> Result<TunnelParameters, Error> { - let tunnel_options = self.settings.get_tunnel_options().clone(); + let tunnel_options = self.settings.tunnel_options.clone(); let location = relay.location.as_ref().expect("Relay has no location set"); self.last_generated_bridge_relay = None; match endpoint { MullvadEndpoint::OpenVpn(endpoint) => { - let proxy_settings = match self.settings.get_bridge_settings() { + let proxy_settings = match &self.settings.bridge_settings { BridgeSettings::Normal(settings) => { let bridge_constraints = InternalBridgeConstraints { location: settings.location.clone(), @@ -1254,7 +1256,8 @@ where fn set_account(&mut self, account_token: Option<String>) -> Result<bool, settings::Error> { let account_changed = self.settings.set_account_token(account_token.clone())?; if account_changed { - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); // Bump account history if a token was set if let Some(token) = account_token.clone() { @@ -1354,7 +1357,8 @@ where Ok(settings_changed) => { Self::oneshot_send(tx, (), "update_relay_settings response"); if settings_changed { - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); info!("Initiating tunnel restart because the relay settings changed"); self.reconnect_tunnel(); } @@ -1369,7 +1373,8 @@ where Ok(settings_changed) => { Self::oneshot_send(tx, (), "set_allow_lan response"); if settings_changed { - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); self.send_tunnel_command(TunnelCommand::AllowLan(allow_lan)); } } @@ -1383,7 +1388,8 @@ where Ok(settings_changed) => { Self::oneshot_send(tx, (), "set_show_beta_releases response"); if settings_changed { - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); } } Err(e) => error!("{}", e.display_chain_with_msg("Unable to save settings")), @@ -1402,7 +1408,8 @@ where Ok(settings_changed) => { Self::oneshot_send(tx, (), "set_block_when_disconnected response"); if settings_changed { - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); self.send_tunnel_command(TunnelCommand::BlockWhenDisconnected( block_when_disconnected, )); @@ -1418,7 +1425,8 @@ where Ok(settings_changed) => { Self::oneshot_send(tx, (), "set auto-connect response"); if settings_changed { - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); } } Err(e) => error!("{}", e.display_chain_with_msg("Unable to save settings")), @@ -1431,7 +1439,8 @@ where Ok(settings_changed) => { Self::oneshot_send(tx, (), "set_openvpn_mssfix response"); if settings_changed { - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); if let Some(TunnelType::OpenVpn) = self.get_connected_tunnel_type() { info!( "Initiating tunnel restart because the OpenVPN mssfix setting changed" @@ -1452,7 +1461,8 @@ where match self.settings.set_bridge_settings(new_settings) { Ok(settings_changes) => { if settings_changes { - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); self.reconnect_tunnel(); }; Self::oneshot_send(tx, Ok(()), "set_bridge_settings"); @@ -1476,7 +1486,8 @@ where let result = match self.settings.set_bridge_state(bridge_state) { Ok(settings_changed) => { if settings_changed { - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); log::info!("Initiating tunnel restart because bridge state changed"); self.reconnect_tunnel(); } @@ -1500,7 +1511,8 @@ where Ok(settings_changed) => { Self::oneshot_send(tx, (), "set_enable_ipv6 response"); if settings_changed { - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); info!("Initiating tunnel restart because the enable IPv6 setting changed"); self.reconnect_tunnel(); } @@ -1515,7 +1527,8 @@ where Ok(settings_changed) => { Self::oneshot_send(tx, (), "set_wireguard_mtu response"); if settings_changed { - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); if let Some(TunnelType::Wireguard) = self.get_connected_tunnel_type() { info!( "Initiating tunnel restart because the WireGuard MTU setting changed" @@ -1548,7 +1561,8 @@ where ); } - self.event_listener.notify_settings(self.settings.clone()); + self.event_listener + .notify_settings(self.settings.to_settings()); } } Err(e) => error!("{}", e.display_chain_with_msg("Unable to save settings")), @@ -1624,7 +1638,7 @@ where &mut self.account_history, account_token, self.settings - .get_tunnel_options() + .tunnel_options .wireguard .automatic_rotation .map(|hours| Duration::from_secs(60u64 * 60u64 * hours as u64)), @@ -1696,7 +1710,7 @@ where } fn on_get_settings(&self, tx: oneshot::Sender<Settings>) { - Self::oneshot_send(tx, self.settings.clone(), "get_settings response"); + Self::oneshot_send(tx, self.settings.to_settings(), "get_settings response"); } fn oneshot_send<T>(tx: oneshot::Sender<T>, t: T, msg: &'static str) { diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs index 8795861dbe..0b02dfbe5e 100644 --- a/mullvad-daemon/src/main.rs +++ b/mullvad-daemon/src/main.rs @@ -111,6 +111,8 @@ fn create_daemon( log_dir: Option<PathBuf>, ) -> Result<Daemon<ManagementInterfaceEventBroadcaster>, String> { let resource_dir = mullvad_paths::get_resource_dir(); + let settings_dir = mullvad_paths::settings_dir() + .map_err(|e| e.display_chain_with_msg("Unable to get settings dir"))?; let cache_dir = mullvad_paths::cache_dir() .map_err(|e| e.display_chain_with_msg("Unable to get cache dir"))?; @@ -120,6 +122,7 @@ fn create_daemon( Daemon::start( log_dir, resource_dir, + settings_dir, cache_dir, event_listener, command_channel, diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index e0f7a94b4e..00254472be 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -13,7 +13,7 @@ use mullvad_types::{ location::GeoIpLocation, relay_constraints::{BridgeSettings, BridgeState, RelaySettingsUpdate}, relay_list::RelayList, - settings::{self, Settings}, + settings::Settings, states::{TargetState, TunnelState}, version, wireguard, DaemonEvent, }; @@ -582,12 +582,7 @@ impl ManagementInterfaceApi for ManagementInterface { let future = self .send_command_to_daemon(DaemonCommand::SetBridgeSettings(tx, bridge_settings)) .and_then(|_| rx.map_err(|_| Error::internal_error())) - .and_then(|settings_result| { - settings_result.map_err(|error| match error { - settings::Error::InvalidProxyData(reason) => Error::invalid_params(reason), - _ => Error::internal_error(), - }) - }); + .and_then(|settings_result| settings_result.map_err(|_| Error::internal_error())); Box::new(future) } diff --git a/mullvad-daemon/src/settings.rs b/mullvad-daemon/src/settings.rs index df0ee0b996..daf3225913 100644 --- a/mullvad-daemon/src/settings.rs +++ b/mullvad-daemon/src/settings.rs @@ -1,80 +1,304 @@ -#[cfg(windows)] -use log::{error, warn}; +use log::{debug, error, info}; +use mullvad_types::{ + relay_constraints::{BridgeSettings, BridgeState, RelaySettingsUpdate}, + settings::Settings, +}; +use std::{ + fs::File, + io::{self, BufReader, Read}, + ops::Deref, + path::{Path, PathBuf}, +}; +use talpid_types::ErrorExt; -use log::info; +#[cfg(not(target_os = "android"))] +use std::fs; #[cfg(windows)] -use mullvad_types::settings::Error as SettingsError; +use {log::warn, talpid_core::logging::windows::log_sink}; -pub use mullvad_types::settings::*; -#[cfg(windows)] -use std::io::ErrorKind; +static SETTINGS_FILE: &str = "settings.json"; -#[cfg(windows)] -use talpid_core::logging::windows::log_sink; -pub fn load() -> Settings { - match Settings::load() { - Ok(mut settings) => { - // Force IPv6 to be enabled on Android - if cfg!(target_os = "android") { - let _ = settings.set_enable_ipv6(true); +#[derive(err_derive::Error, Debug)] +pub enum Error { + #[error(display = "Unable to remove settings file {}", _0)] + #[cfg(not(target_os = "android"))] + DeleteError(String, #[error(source)] io::Error), + + #[error(display = "Unable to serialize settings to JSON")] + SerializeError(#[error(source)] serde_json::Error), + + #[error(display = "Unable to write settings to {}", _0)] + WriteError(String, #[error(source)] io::Error), +} + +#[derive(Debug)] +enum LoadSettingsError { + FileNotFound, + Other, +} + + +#[derive(Debug)] +pub struct SettingsPersister { + settings: Settings, + path: PathBuf, +} + +impl SettingsPersister { + /// Loads user settings from file. If no file is present it returns the defaults. + pub fn load(settings_dir: &Path) -> Self { + let path = settings_dir.join(SETTINGS_FILE); + let (mut settings, mut should_save) = Self::load_settings(&path); + + // Force IPv6 to be enabled on Android + if cfg!(target_os = "android") { + should_save |= + Self::update_field(&mut settings.tunnel_options.generic.enable_ipv6, true); + } + + let mut persister = SettingsPersister { settings, path }; + + if should_save { + if let Err(error) = persister.save() { + error!( + "{}", + error.display_chain_with_msg("Failed to save updated settings") + ); } - settings } - #[cfg(windows)] - Err(SettingsError::ReadError(ref _path, ref e)) if e.kind() == ErrorKind::NotFound => { - info!( - "No settings file found. Attempting migration from Windows Update backup location" - ); - if migrate_after_windows_update() { - match Settings::load() { - Ok(settings) => { - info!("Successfully loaded migrated settings"); - settings - } - Err(_) => { - warn!("Failed to load migrated settings, using defaults"); - Settings::default() - } + + persister + } + + fn load_settings(path: &Path) -> (Settings, bool) { + Self::load_settings_from_file(path) + .or_else(|error| match error { + #[cfg(windows)] + LoadSettingsError::FileNotFound => { + Self::try_load_settings_after_windows_update(path) } + _ => Err(error), + }) + .unwrap_or_else(|_| { + info!("Failed to load settings, using defaults"); + (Settings::default(), true) + }) + } + + fn load_settings_from_file(path: &Path) -> Result<(Settings, bool), LoadSettingsError> { + let file = File::open(path).map_err(|error| { + if error.kind() == io::ErrorKind::NotFound { + LoadSettingsError::FileNotFound } else { - info!("Failed to migrate settings, using defaults"); - Settings::default() + LoadSettingsError::Other + } + })?; + + info!("Loading settings from {}", path.display()); + let mut settings_bytes = vec![]; + BufReader::new(file) + .read_to_end(&mut settings_bytes) + .map_err(|_| LoadSettingsError::Other)?; + + Settings::load_from_bytes(&settings_bytes) + .map(|settings| (settings, false)) + .or_else(|error| { + log::error!( + "{}", + error.display_chain_with_msg("Failed to parse settings file") + ); + Settings::migrate_from_bytes(&settings_bytes).map(|settings| (settings, true)) + }) + .map_err(|_| LoadSettingsError::Other) + } + + #[cfg(windows)] + fn try_load_settings_after_windows_update( + path: &Path, + ) -> Result<(Settings, bool), LoadSettingsError> { + info!("No settings file found. Attempting migration from Windows Update backup location"); + + if Self::migrate_after_windows_update() { + let result = Self::load_settings_from_file(path); + + match &result { + Ok(_) => info!("Successfully loaded migrated settings"), + Err(_) => warn!("Failed to load migrated settings, using defaults"), } + + result + } else { + Err(LoadSettingsError::Other) } - Err(_) => { - info!("Failed to load settings, using defaults"); - Settings::default() + } + + #[cfg(windows)] + fn migrate_after_windows_update() -> bool { + match unsafe { + ffi::WinUtil_MigrateAfterWindowsUpdate(Some(log_sink), b"Settings migrator\0".as_ptr()) + } { + ffi::WinUtilMigrationStatus::Success => { + info!("Migration completed successfully"); + true + } + ffi::WinUtilMigrationStatus::Aborted => { + error!("Migration was aborted to avoid overwriting current settings"); + false + } + ffi::WinUtilMigrationStatus::NothingToMigrate => { + info!("Could not migrate settings - no backup present"); + false + } + ffi::WinUtilMigrationStatus::Failed | _ => { + error!("Migration failed"); + false + } } } -} -#[cfg(windows)] -fn migrate_after_windows_update() -> bool { - match unsafe { - ffi::WinUtil_MigrateAfterWindowsUpdate(Some(log_sink), b"Settings migrator\0".as_ptr()) - } { - ffi::WinUtilMigrationStatus::Success => { - info!("Migration completed successfully"); + /// Serializes the settings and saves them to the file it was loaded from. + fn save(&mut self) -> Result<(), Error> { + debug!("Writing settings to {}", self.path.display()); + let mut file = File::create(&self.path) + .map_err(|e| Error::WriteError(self.path.display().to_string(), e))?; + + serde_json::to_writer_pretty(&mut file, &self.settings).map_err(Error::SerializeError)?; + file.sync_all() + .map_err(|e| Error::WriteError(self.path.display().to_string(), e)) + } + + /// Resets default settings + #[cfg(not(target_os = "android"))] + pub fn reset(&mut self) -> Result<(), Error> { + self.settings = Settings::default(); + self.save().or_else(|e| { + log::error!( + "{}", + e.display_chain_with_msg("Unable to save default settings") + ); + log::info!("Will attempt to remove settings file"); + fs::remove_file(&self.path) + .map_err(|e| Error::DeleteError(self.path.display().to_string(), e)) + }) + } + + pub fn to_settings(&self) -> Settings { + self.settings.clone() + } + + /// Changes account number to the one given. Also saves the new settings to disk. + /// The boolean in the Result indicates if the account token changed or not + pub fn set_account_token(&mut self, account_token: Option<String>) -> Result<bool, Error> { + let should_save = self.settings.set_account_token(account_token); + self.update(should_save) + } + + pub fn update_relay_settings(&mut self, update: RelaySettingsUpdate) -> Result<bool, Error> { + let should_save = self.settings.update_relay_settings(update); + self.update(should_save) + } + + pub fn set_allow_lan(&mut self, allow_lan: bool) -> Result<bool, Error> { + let should_save = Self::update_field(&mut self.settings.allow_lan, allow_lan); + self.update(should_save) + } + + pub fn set_block_when_disconnected( + &mut self, + block_when_disconnected: bool, + ) -> Result<bool, Error> { + let should_save = Self::update_field( + &mut self.settings.block_when_disconnected, + block_when_disconnected, + ); + self.update(should_save) + } + + pub fn set_auto_connect(&mut self, auto_connect: bool) -> Result<bool, Error> { + let should_save = Self::update_field(&mut self.settings.auto_connect, auto_connect); + self.update(should_save) + } + + pub fn set_openvpn_mssfix(&mut self, openvpn_mssfix: Option<u16>) -> Result<bool, Error> { + let should_save = Self::update_field( + &mut self.settings.tunnel_options.openvpn.mssfix, + openvpn_mssfix, + ); + self.update(should_save) + } + + pub fn set_enable_ipv6(&mut self, enable_ipv6: bool) -> Result<bool, Error> { + let should_save = Self::update_field( + &mut self.settings.tunnel_options.generic.enable_ipv6, + enable_ipv6, + ); + self.update(should_save) + } + + pub fn set_wireguard_mtu(&mut self, mtu: Option<u16>) -> Result<bool, Error> { + let should_save = Self::update_field(&mut self.settings.tunnel_options.wireguard.mtu, mtu); + self.update(should_save) + } + + pub fn set_wireguard_rotation_interval( + &mut self, + automatic_rotation: Option<u32>, + ) -> Result<bool, Error> { + let should_save = Self::update_field( + &mut self.settings.tunnel_options.wireguard.automatic_rotation, + automatic_rotation, + ); + self.update(should_save) + } + + pub fn set_show_beta_releases(&mut self, show_beta_releases: bool) -> Result<bool, Error> { + let should_save = Self::update_field( + &mut self.settings.show_beta_releases, + Some(show_beta_releases), + ); + self.update(should_save) + } + + pub fn set_bridge_settings(&mut self, bridge_settings: BridgeSettings) -> Result<bool, Error> { + let should_save = Self::update_field(&mut self.settings.bridge_settings, bridge_settings); + self.update(should_save) + } + + pub fn set_bridge_state(&mut self, bridge_state: BridgeState) -> Result<bool, Error> { + let should_save = self.settings.set_bridge_state(bridge_state); + self.update(should_save) + } + + fn update_field<T: Eq>(field: &mut T, new_value: T) -> bool { + if *field != new_value { + *field = new_value; true - } - ffi::WinUtilMigrationStatus::Aborted => { - error!("Migration was aborted to avoid overwriting current settings"); + } else { false } - ffi::WinUtilMigrationStatus::NothingToMigrate => { - info!("Could not migrate settings - no backup present"); - false - } - ffi::WinUtilMigrationStatus::Failed | _ => { - error!("Migration failed"); - false + } + + fn update(&mut self, should_save: bool) -> Result<bool, Error> { + if should_save { + self.save().map(|_| true) + } else { + Ok(false) } } } +impl Deref for SettingsPersister { + type Target = Settings; + + fn deref(&self) -> &Self::Target { + &self.settings + } +} + + #[cfg(windows)] mod ffi { use talpid_core::logging::windows::LogSink; diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs index a95b4ae308..b3996ee507 100644 --- a/mullvad-jni/src/lib.rs +++ b/mullvad-jni/src/lib.rs @@ -16,7 +16,6 @@ use jnix::{ }, FromJava, IntoJava, JnixEnv, }; -use lazy_static::lazy_static; use mullvad_daemon::{exception_logging, logging, version, Daemon, DaemonCommandChannel}; use mullvad_types::account::AccountData; use std::{ @@ -29,12 +28,9 @@ use talpid_types::{android::AndroidContext, ErrorExt}; const LOG_FILENAME: &str = "daemon.log"; -lazy_static! { - static ref LOG_INIT_RESULT: Result<PathBuf, String> = - start_logging().map_err(|error| error.display_chain()); -} - static LOAD_CLASSES: Once = Once::new(); +static LOG_START: Once = Once::new(); +static mut LOG_INIT_RESULT: Option<Result<(), String>> = None; #[derive(Debug, err_derive::Error)] #[error(no_from)] @@ -42,23 +38,14 @@ pub enum Error { #[error(display = "Failed to create global reference to Java object")] CreateGlobalReference(#[error(cause)] jnix::jni::errors::Error), - #[error(display = "Failed to get cache directory path")] - GetCacheDir(#[error(source)] mullvad_paths::Error), - #[error(display = "Failed to get Java VM instance")] GetJvmInstance(#[error(cause)] jnix::jni::errors::Error), - #[error(display = "Failed to get log directory path")] - GetLogDir(#[error(source)] mullvad_paths::Error), - #[error(display = "Failed to initialize the mullvad daemon")] InitializeDaemon(#[error(source)] mullvad_daemon::Error), #[error(display = "Failed to spawn the JNI event listener")] SpawnJniEventListener(#[error(source)] jni_event_listener::Error), - - #[error(display = "Failed to start logger")] - StartLogging(#[error(source)] logging::Error), } #[derive(IntoJava)] @@ -95,47 +82,67 @@ pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_initial env: JNIEnv<'_>, this: JObject<'_>, vpnService: JObject<'_>, + cacheDirectory: JObject<'_>, + resourceDirectory: JObject<'_>, ) { let env = JnixEnv::from(env); + let cache_dir = PathBuf::from(String::from_java(&env, cacheDirectory)); + let resource_dir = PathBuf::from(String::from_java(&env, resourceDirectory)); - match *LOG_INIT_RESULT { - Ok(ref log_dir) => { + match start_logging(&resource_dir) { + Ok(()) => { LOAD_CLASSES.call_once(|| env.preload_classes(classes::CLASSES.iter().cloned())); - if let Err(error) = initialize(&env, &this, &vpnService, log_dir.clone()) { + if let Err(error) = initialize(&env, &this, &vpnService, cache_dir, resource_dir) { log::error!("{}", error.display_chain()); } } - Err(ref message) => env + Err(message) => env .throw(message.as_str()) .expect("Failed to throw exception"), } } -fn start_logging() -> Result<PathBuf, Error> { - let log_dir = mullvad_paths::log_dir().map_err(Error::GetLogDir)?; +fn start_logging(log_dir: &Path) -> Result<(), String> { + unsafe { + LOG_START.call_once(|| LOG_INIT_RESULT = Some(initialize_logging(log_dir))); + LOG_INIT_RESULT + .clone() + .expect("Logging not properly initialized") + } +} + +fn initialize_logging(log_dir: &Path) -> Result<(), String> { let log_file = log_dir.join(LOG_FILENAME); logging::init_logger(log::LevelFilter::Debug, Some(&log_file), true) - .map_err(Error::StartLogging)?; + .map_err(|error| error.display_chain_with_msg("Failed to start logger"))?; exception_logging::enable(); log_panics::init(); version::log_version(); - Ok(log_dir) + Ok(()) } fn initialize( env: &JnixEnv<'_>, this: &JObject<'_>, vpn_service: &JObject<'_>, - log_dir: PathBuf, + cache_dir: PathBuf, + resource_dir: PathBuf, ) -> Result<(), Error> { let android_context = create_android_context(env, *vpn_service)?; let daemon_command_channel = DaemonCommandChannel::new(); let daemon_interface = Box::new(DaemonInterface::new(daemon_command_channel.sender())); - spawn_daemon(env, this, log_dir, daemon_command_channel, android_context)?; + spawn_daemon( + env, + this, + cache_dir, + resource_dir, + daemon_command_channel, + android_context, + )?; set_daemon_interface_address(env, this, Box::into_raw(daemon_interface) as jlong); @@ -157,7 +164,8 @@ fn create_android_context( fn spawn_daemon( env: &JnixEnv<'_>, this: &JObject<'_>, - log_dir: PathBuf, + cache_dir: PathBuf, + resource_dir: PathBuf, command_channel: DaemonCommandChannel, android_context: AndroidContext, ) -> Result<(), Error> { @@ -169,8 +177,17 @@ fn spawn_daemon( thread::spawn(move || { let jvm = android_context.jvm.clone(); + let daemon = Daemon::start( + Some(resource_dir.clone()), + resource_dir.clone(), + resource_dir, + cache_dir, + listener, + command_channel, + android_context, + ); - match create_daemon(listener, log_dir, command_channel, android_context) { + match daemon { Ok(daemon) => { let _ = tx.send(Ok(())); match daemon.run() { @@ -179,7 +196,7 @@ fn spawn_daemon( } } Err(error) => { - let _ = tx.send(Err(error)); + let _ = tx.send(Err(Error::InitializeDaemon(error))); } } @@ -189,26 +206,6 @@ fn spawn_daemon( rx.recv().unwrap() } -fn create_daemon( - listener: JniEventListener, - log_dir: PathBuf, - command_channel: DaemonCommandChannel, - android_context: AndroidContext, -) -> Result<Daemon<JniEventListener>, Error> { - let resource_dir = mullvad_paths::get_resource_dir(); - let cache_dir = mullvad_paths::cache_dir().map_err(Error::GetCacheDir)?; - - Daemon::start( - Some(log_dir), - resource_dir, - cache_dir, - listener, - command_channel, - android_context, - ) - .map_err(Error::InitializeDaemon) -} - fn notify_daemon_stopped(jvm: Arc<JavaVM>, daemon_object: GlobalRef) { match jvm.attach_current_thread_as_daemon() { Ok(env) => { @@ -801,13 +798,16 @@ pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_updateR pub extern "system" fn Java_net_mullvad_mullvadvpn_dataproxy_MullvadProblemReport_collectReport( env: JNIEnv<'_>, _: JObject<'_>, + logDirectory: JString<'_>, outputPath: JString<'_>, ) -> jboolean { let env = JnixEnv::from(env); + let log_dir_string = String::from_java(&env, logDirectory); + let log_dir = Path::new(&log_dir_string); let output_path_string = String::from_java(&env, outputPath); let output_path = Path::new(&output_path_string); - match mullvad_problem_report::collect_report(&[], output_path, Vec::new()) { + match mullvad_problem_report::collect_report(&[], output_path, Vec::new(), log_dir) { Ok(()) => JNI_TRUE, Err(error) => { log::error!( diff --git a/mullvad-problem-report/src/lib.rs b/mullvad-problem-report/src/lib.rs index 96d181bc11..bac11df5dc 100644 --- a/mullvad-problem-report/src/lib.rs +++ b/mullvad-problem-report/src/lib.rs @@ -100,12 +100,22 @@ pub fn collect_report( extra_logs: &[&Path], output_path: &Path, redact_custom_strings: Vec<String>, + #[cfg(target_os = "android")] android_log_dir: &Path, ) -> Result<(), Error> { let mut problem_report = ProblemReport::new(redact_custom_strings); - let daemon_logs = mullvad_paths::get_log_dir() - .map_err(LogError::GetLogDir) - .and_then(list_logs); + let daemon_logs_dir = { + #[cfg(target_os = "android")] + { + Ok(android_log_dir.to_owned()) + } + #[cfg(not(target_os = "android"))] + { + mullvad_paths::get_log_dir().map_err(LogError::GetLogDir) + } + }; + + let daemon_logs = daemon_logs_dir.and_then(list_logs); match daemon_logs { Ok(daemon_logs) => { let mut other_logs = Vec::new(); @@ -144,7 +154,7 @@ pub fn collect_report( None => {} } #[cfg(target_os = "android")] - match write_logcat_to_file() { + match write_logcat_to_file(android_log_dir) { Ok(logcat_path) => problem_report.add_log(&logcat_path), Err(error) => problem_report.add_error("Failed to collect logcat", &error), } @@ -227,8 +237,8 @@ fn is_tunnel_log(path: &Path) -> bool { } #[cfg(target_os = "android")] -fn write_logcat_to_file() -> Result<PathBuf, io::Error> { - let logcat_path = PathBuf::from("/data/data/net.mullvad.mullvadvpn/logcat.txt"); +fn write_logcat_to_file(log_dir: &Path) -> Result<PathBuf, io::Error> { + let logcat_path = log_dir.join("logcat.txt"); duct::cmd!("logcat", "-d") .stderr_to_stdout() diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index b7beecac85..269fc93cf4 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -7,15 +7,7 @@ use jnix::IntoJava; use log::{debug, info}; use serde::{Deserialize, Serialize}; use serde_json; -use std::{ - fs::{self, File}, - io::{self, Read}, - path::PathBuf, -}; -use talpid_types::{ - net::{openvpn, wireguard, GenericTunnelOptions}, - ErrorExt, -}; +use talpid_types::net::{openvpn, wireguard, GenericTunnelOptions}; mod migrations; @@ -24,33 +16,13 @@ pub type Result<T> = std::result::Result<T, Error>; #[derive(err_derive::Error, Debug)] #[error(no_from)] pub enum Error { - #[error(display = "Unable to create settings directory")] - DirectoryError(#[error(source)] mullvad_paths::Error), - - #[error(display = "Unable to read settings from {}", _0)] - ReadError(String, #[error(source)] io::Error), - - #[error(display = "Unable to remove settings file {}", _0)] - DeleteError(String, #[error(source)] io::Error), - #[error(display = "Malformed settings")] ParseError(#[error(source)] serde_json::Error), - #[error(display = "Unable to serialize settings to JSON")] - SerializeError(#[error(source)] serde_json::Error), - - #[error(display = "Unable to write settings to {}", _0)] - WriteError(String, #[error(source)] io::Error), - - #[error(display = "Invalid OpenVPN proxy configuration: {}", _0)] - InvalidProxyData(String), - #[error(display = "Unable to read any version of the settings")] NoMatchingVersion, } -static SETTINGS_FILE: &str = "settings.json"; - /// Mullvad daemon settings. #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] @@ -61,22 +33,22 @@ pub struct Settings { account_token: Option<String>, relay_settings: RelaySettings, #[cfg_attr(target_os = "android", jnix(skip))] - bridge_settings: BridgeSettings, + pub bridge_settings: BridgeSettings, #[cfg_attr(target_os = "android", jnix(skip))] bridge_state: BridgeState, /// If the daemon should allow communication with private (LAN) networks. - allow_lan: bool, + pub allow_lan: bool, /// Extra level of kill switch. When this setting is on, the disconnected state will block /// the firewall to not allow any traffic in or out. #[cfg_attr(target_os = "android", jnix(skip))] - block_when_disconnected: bool, + pub block_when_disconnected: bool, /// If the daemon should connect the VPN tunnel directly on start or not. - auto_connect: bool, + pub auto_connect: bool, /// Options that should be applied to tunnels of a specific type regardless of where the relays /// might be located. - tunnel_options: TunnelOptions, + pub tunnel_options: TunnelOptions, /// Whether to notify users of beta updates. - show_beta_releases: Option<bool>, + pub show_beta_releases: Option<bool>, /// Specifies settings schema version #[cfg_attr(target_os = "android", jnix(skip))] settings_version: migrations::SettingsVersion, @@ -105,68 +77,12 @@ impl Default for Settings { } impl Settings { - /// Loads user settings from file. If no file is present it returns the defaults. - pub fn load() -> Result<Settings> { - let path = Self::get_settings_path()?; - match File::open(&path) { - Ok(file) => { - info!("Loading settings from {}", path.display()); - let mut settings_bytes = vec![]; - io::BufReader::new(file) - .read_to_end(&mut settings_bytes) - .map_err(|e| Error::ReadError("Failed to read settings file".to_owned(), e))?; - Self::parse_settings(&mut settings_bytes).or_else(|e| { - log::error!( - "{}", - e.display_chain_with_msg("Failed to parse settings file") - ); - let settings = migrations::try_migrate_settings(&settings_bytes)?; - if let Err(e) = settings.save() { - log::error!("Failed to save settings after migration: {}", e); - } - Ok(settings) - }) - } - Err(e) => Err(Error::ReadError(path.display().to_string(), e)), - } - } - - /// Serializes the settings and saves them to the file it was loaded from. - fn save(&self) -> Result<()> { - let path = Self::get_settings_path()?; - - debug!("Writing settings to {}", path.display()); - let mut file = - File::create(&path).map_err(|e| Error::WriteError(path.display().to_string(), e))?; - - serde_json::to_writer_pretty(&mut file, self).map_err(Error::SerializeError)?; - file.sync_all() - .map_err(|e| Error::WriteError(path.display().to_string(), e)) - } - - /// Resets default settings - pub fn reset(&mut self) -> Result<()> { - *self = Default::default(); - self.save().or_else(|e| { - log::error!( - "{}", - e.display_chain_with_msg("Unable to save default settings") - ); - log::error!("Will attempt to remove settings file"); - Self::get_settings_path().and_then(|path| { - fs::remove_file(&path) - .map_err(|e| Error::DeleteError(path.display().to_string(), e)) - }) - }) + pub fn load_from_bytes(bytes: &[u8]) -> Result<Self> { + serde_json::from_slice(bytes).map_err(Error::ParseError) } - fn get_settings_path() -> Result<PathBuf> { - let dir = ::mullvad_paths::settings_dir().map_err(Error::DirectoryError)?; - Ok(dir.join(SETTINGS_FILE)) - } - - fn parse_settings(bytes: &[u8]) -> Result<Settings> { - serde_json::from_slice(bytes).map_err(Error::ParseError) + pub fn migrate_from_bytes(bytes: &[u8]) -> Result<Self> { + migrations::try_migrate_settings(&bytes) } pub fn get_account_token(&self) -> Option<String> { @@ -175,7 +91,7 @@ impl Settings { /// Changes account number to the one given. Also saves the new settings to disk. /// The boolean in the Result indicates if the account token changed or not - pub fn set_account_token(&mut self, mut account_token: Option<String>) -> Result<bool> { + pub fn set_account_token(&mut self, mut account_token: Option<String>) -> bool { if account_token.as_ref().map(String::len) == Some(0) { debug!("Setting empty account token is treated as unsetting it"); account_token = None; @@ -189,9 +105,9 @@ impl Settings { info!("Changing account token") } self.account_token = account_token; - self.save().map(|_| true) + true } else { - Ok(false) + false } } @@ -199,7 +115,7 @@ impl Settings { self.relay_settings.clone() } - pub fn update_relay_settings(&mut self, update: RelaySettingsUpdate) -> Result<bool> { + pub fn update_relay_settings(&mut self, update: RelaySettingsUpdate) -> bool { let update_supports_bridge = update.supports_bridge(); let new_settings = self.relay_settings.merge(update); if self.relay_settings != new_settings { @@ -212,117 +128,9 @@ impl Settings { ); self.relay_settings = new_settings; - self.save().map(|_| true) - } else { - Ok(false) - } - } - - pub fn get_allow_lan(&self) -> bool { - self.allow_lan - } - - pub fn set_allow_lan(&mut self, allow_lan: bool) -> Result<bool> { - if allow_lan != self.allow_lan { - self.allow_lan = allow_lan; - self.save().map(|_| true) - } else { - Ok(false) - } - } - - pub fn get_block_when_disconnected(&self) -> bool { - self.block_when_disconnected - } - - pub fn set_block_when_disconnected(&mut self, block_when_disconnected: bool) -> Result<bool> { - if block_when_disconnected != self.block_when_disconnected { - self.block_when_disconnected = block_when_disconnected; - self.save().map(|_| true) - } else { - Ok(false) - } - } - - pub fn get_auto_connect(&self) -> bool { - self.auto_connect - } - - pub fn set_auto_connect(&mut self, auto_connect: bool) -> Result<bool> { - if auto_connect != self.auto_connect { - self.auto_connect = auto_connect; - self.save().map(|_| true) - } else { - Ok(false) - } - } - - pub fn set_openvpn_mssfix(&mut self, openvpn_mssfix: Option<u16>) -> Result<bool> { - if self.tunnel_options.openvpn.mssfix != openvpn_mssfix { - self.tunnel_options.openvpn.mssfix = openvpn_mssfix; - self.save().map(|_| true) - } else { - Ok(false) - } - } - - pub fn set_enable_ipv6(&mut self, enable_ipv6: bool) -> Result<bool> { - if self.tunnel_options.generic.enable_ipv6 != enable_ipv6 { - self.tunnel_options.generic.enable_ipv6 = enable_ipv6; - self.save().map(|_| true) - } else { - Ok(false) - } - } - - pub fn set_wireguard_mtu(&mut self, mtu: Option<u16>) -> Result<bool> { - if self.tunnel_options.wireguard.mtu != mtu { - self.tunnel_options.wireguard.mtu = mtu; - self.save().map(|_| true) - } else { - Ok(false) - } - } - - pub fn set_wireguard_rotation_interval( - &mut self, - automatic_rotation: Option<u32>, - ) -> Result<bool> { - if self.tunnel_options.wireguard.automatic_rotation != automatic_rotation { - self.tunnel_options.wireguard.automatic_rotation = automatic_rotation; - self.save().map(|_| true) - } else { - Ok(false) - } - } - - pub fn get_tunnel_options(&self) -> &TunnelOptions { - &self.tunnel_options - } - - pub fn get_show_beta_releases(&self) -> Option<bool> { - self.show_beta_releases.clone() - } - - pub fn set_show_beta_releases(&mut self, enabled: bool) -> Result<bool> { - if Some(enabled) != self.show_beta_releases { - self.show_beta_releases = Some(enabled); - self.save().map(|_| true) - } else { - Ok(false) - } - } - - pub fn get_bridge_settings(&self) -> &BridgeSettings { - &self.bridge_settings - } - - pub fn set_bridge_settings(&mut self, bridge_settings: BridgeSettings) -> Result<bool> { - if self.bridge_settings != bridge_settings { - self.bridge_settings = bridge_settings; - self.save().map(|_| true) + true } else { - Ok(false) + false } } @@ -330,15 +138,15 @@ impl Settings { &self.bridge_state } - pub fn set_bridge_state(&mut self, bridge_state: BridgeState) -> Result<bool> { + pub fn set_bridge_state(&mut self, bridge_state: BridgeState) -> bool { if self.bridge_state != bridge_state { self.bridge_state = bridge_state; if self.bridge_state == BridgeState::On { self.relay_settings.ensure_bridge_compatibility(); } - self.save().map(|_| true) + true } else { - Ok(false) + false } } } |
