diff options
Diffstat (limited to 'src/cryptsetup.rs')
-rw-r--r-- | src/cryptsetup.rs | 349 |
1 files changed, 128 insertions, 221 deletions
diff --git a/src/cryptsetup.rs b/src/cryptsetup.rs index bfe2fa6..19bf592 100644 --- a/src/cryptsetup.rs +++ b/src/cryptsetup.rs @@ -1,88 +1,28 @@ -use std::io::Write; use std::process::{Command, Stdio}; +use anyhow::{anyhow, Context, Result}; use log::info; use sysinfo::{DiskExt, System, SystemExt}; -use crate::fm_error::{FmError, FmResult}; use crate::impl_selectable_content; +use crate::mount_help::MountHelper; +use crate::password::{sudo, sudo_password, PasswordHolder, PasswordKind}; use crate::utils::current_username; -/// Different kind of password -#[derive(Debug, Clone, Copy)] -pub enum PasswordKind { - SUDO, - CRYPTSETUP, -} - /// Possible actions on encrypted drives #[derive(Debug, Clone, Copy)] -pub enum EncryptedAction { +pub enum BlockDeviceAction { MOUNT, UMOUNT, } -impl std::fmt::Display for PasswordKind { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let asker = match self { - Self::SUDO => "sudo ", - Self::CRYPTSETUP => "device ", - }; - write!(f, "{asker}") - } -} - -/// Holds passwords allowing to mount or unmount an encrypted drive. -#[derive(Default, Clone, Debug)] -pub struct PasswordHolder { - sudo: Option<String>, - cryptsetup: Option<String>, -} - -impl PasswordHolder { - fn set_sudo(&mut self, password: String) { - self.sudo = Some(password) - } - - fn set_cryptsetup(&mut self, passphrase: String) { - self.cryptsetup = Some(passphrase) - } - - /// Reads the cryptsetup password - fn cryptsetup(&self) -> FmResult<String> { - self.cryptsetup - .clone() - .ok_or_else(|| FmError::custom("PasswordHolder", "cryptsetup password isn't set")) - } - - /// Reads the sudo password - fn sudo(&self) -> FmResult<String> { - self.sudo - .clone() - .ok_or_else(|| FmError::custom("PasswordHolder", "sudo password isn't set")) - } - - fn has_sudo(&self) -> bool { - self.sudo.is_some() - } - - fn has_cryptsetup(&self) -> bool { - self.cryptsetup.is_some() - } - - fn reset(&mut self) { - self.sudo = None; - self.cryptsetup = None; - } -} - /// get devices list from lsblk /// Return the output of /// ```bash /// lsblk -l -o FSTYPE,PATH,UUID,FSVER,MOUNTPOINT,PARTLABEL /// ``` /// as a String. -fn get_devices() -> FmResult<String> { +fn get_devices() -> Result<String> { let output = Command::new("lsblk") .args(&vec!["-l", "-o", "FSTYPE,PATH,UUID,FSVER,MOUNTPOINT"]) .stdin(Stdio::null()) @@ -101,49 +41,6 @@ fn filter_crypto_devices_lines(output: String, key: &str) -> Vec<String> { .collect() } -/// run a sudo command requiring a password (generally to establish the password.) -/// Since I can't send 2 passwords at a time, it will only work with the sudo password -fn sudo_password(args: &[String], password: &str) -> FmResult<(bool, String, String)> { - info!("sudo {:?}", args); - let mut child = Command::new("sudo") - .args(args) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let child_stdin = child - .stdin - .as_mut() - .ok_or_else(|| FmError::custom("run_privileged_command", "couldn't open child stdin"))?; - child_stdin.write_all(format!("{password}\n").as_bytes())?; - - let output = child.wait_with_output()?; - Ok(( - output.status.success(), - String::from_utf8(output.stdout)?, - String::from_utf8(output.stderr)?, - )) -} - -/// Run a passwordless sudo command. -/// Returns stdout & stderr -fn sudo(args: &[String]) -> FmResult<(bool, String, String)> { - info!("sudo {:?}", args); - let child = Command::new("sudo") - .args(args) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - let output = child.wait_with_output()?; - Ok(( - output.status.success(), - String::from_utf8(output.stdout)?, - String::from_utf8(output.stderr)?, - )) -} - /// Represent an encrypted device. /// Those attributes comes from cryptsetup. #[derive(Debug, Default, Clone)] @@ -158,13 +55,13 @@ pub struct CryptoDevice { impl CryptoDevice { /// Parse the output of a lsblk formated line into a struct - fn from_line(line: &str) -> FmResult<Self> { + fn from_line(line: &str) -> Result<Self> { let mut crypo_device = Self::default(); crypo_device.update_from_line(line)?; Ok(crypo_device) } - fn update_from_line(&mut self, line: &str) -> FmResult<()> { + fn update_from_line(&mut self, line: &str) -> Result<()> { let strings = line.split_whitespace(); let mut params: Vec<Option<String>> = vec![None; 5]; for (count, param) in strings.enumerate() { @@ -172,16 +69,16 @@ impl CryptoDevice { } self.fs_type = params .remove(0) - .ok_or_else(|| FmError::custom("CryptoDevice", "parameter shouldn't be None"))?; + .context("CryptoDevice: parameter shouldn't be None")?; self.path = params .remove(0) - .ok_or_else(|| FmError::custom("CryptoDevice", "parameter shouldn't be None"))?; + .context("CryptoDevice: parameter shouldn't be None")?; self.uuid = params .remove(0) - .ok_or_else(|| FmError::custom("CryptoDevice", "parameter shouldn't be None"))?; + .context("CryptoDevice: parameter shouldn't be None")?; self.fs_ver = params .remove(0) - .ok_or_else(|| FmError::custom("CryptoDevice", "parameter shouldn't be None"))?; + .context("CryptoDevice: parameter shouldn't be None")?; self.mountpoints = params.remove(0); Ok(()) } @@ -194,48 +91,6 @@ impl CryptoDevice { self.uuid.clone(), ] } - - fn format_mkdir_parameters(&self, username: &str) -> [String; 3] { - [ - "mkdir".to_owned(), - "-p".to_owned(), - format!( - "/run/media/{}/{}", - username, - self.device_name - .clone() - .unwrap_or_else(|| self.uuid.clone()) - ), - ] - } - - fn format_mount_parameters(&self, username: &str) -> [String; 3] { - [ - "mount".to_owned(), - format!("/dev/mapper/{}", self.uuid), - format!( - "/run/media/{}/{}", - username, - self.device_name - .clone() - .unwrap_or_else(|| self.uuid.clone()) - ), - ] - } - - fn format_umount_parameters(&self, username: &str) -> [String; 2] { - [ - "umount".to_owned(), - format!( - "/run/media/{}/{}", - username, - self.device_name - .clone() - .unwrap_or_else(|| self.uuid.clone()) - ), - ] - } - fn format_luksclose_parameters(&self) -> [String; 3] { [ "cryptsetup".to_owned(), @@ -258,14 +113,7 @@ impl CryptoDevice { .find(|s| s.contains(&self.uuid)) } - /// True if there's a mount point for this drive. - /// It's only valid if we mounted the device since it requires - /// the uuid to be in the mount point. - pub fn is_mounted(&self) -> bool { - self.mount_point().is_some() - } - - fn set_device_name(&mut self) -> FmResult<()> { + fn set_device_name(&mut self) -> Result<()> { let child = Command::new("lsblk") .arg("-l") .arg("-n") @@ -285,7 +133,7 @@ impl CryptoDevice { self.device_name = Some( s.split_whitespace() .next() - .ok_or_else(|| FmError::custom("mapped point", "shouldn't be empty"))? + .context("mapped point: shouldn't be empty")? .to_owned(), ); } else { @@ -294,70 +142,34 @@ impl CryptoDevice { Ok(()) } - /// String representation of the device. - pub fn as_string(&self) -> FmResult<String> { - Ok(if let Some(mount_point) = self.mount_point() { - format!("{} -> {}", self.path, mount_point) - } else { - format!("{} - not mounted", self.path) - }) - } - - fn open_mount(&mut self, username: &str, passwords: &mut PasswordHolder) -> FmResult<bool> { + fn open_mount(&mut self, username: &str, password: &mut PasswordHolder) -> Result<bool> { self.set_device_name()?; if self.is_mounted() { - Err(FmError::custom( - "luks open mount", - "device is already mounted", - )) + Err(anyhow!("luks open mount: device is already mounted")) } else { // sudo let (success, _, _) = sudo_password( &["-S".to_owned(), "ls".to_owned(), "/root".to_owned()], - &passwords.sudo()?, + &password.sudo()?, )?; if !success { return Ok(false); } // open let (success, stdout, stderr) = - sudo_password(&self.format_luksopen_parameters(), &passwords.cryptsetup()?)?; - info!("stdout: {}\nstderr: {}", stdout, stderr); - if !success { - return Ok(false); - } - self.set_device_name()?; - // mkdir - let (success, stdout, stderr) = sudo(&self.format_mkdir_parameters(username))?; - info!("stdout: {}\nstderr: {}", stdout, stderr); - if !success { - return Ok(false); - } - // mount - let (success, stdout, stderr) = sudo(&self.format_mount_parameters(username))?; + sudo_password(&self.format_luksopen_parameters(), &password.cryptsetup()?)?; info!("stdout: {}\nstderr: {}", stdout, stderr); if !success { return Ok(false); } - // sudo -k - sudo(&["-k".to_owned()])?; - Ok(success) + self.mount(username, password) } } - fn umount_close(&mut self, username: &str, passwords: &mut PasswordHolder) -> FmResult<bool> { - self.set_device_name()?; - // sudo - let (success, _, _) = sudo_password( - &["-S".to_owned(), "ls".to_owned(), "/root".to_owned()], - &passwords.sudo()?, - )?; - if !success { + fn umount_close(&mut self, username: &str, password: &mut PasswordHolder) -> Result<bool> { + if !self.umount(username, password)? { return Ok(false); } - // unmount - let (_, stdout, stderr) = sudo(&self.format_umount_parameters(username))?; - info!("stdout: {}\nstderr: {}", stdout, stderr); // close let (success, stdout, stderr) = sudo(&self.format_luksclose_parameters())?; info!("stdout: {}\nstderr: {}", stdout, stderr); @@ -371,16 +183,111 @@ impl CryptoDevice { } } +impl MountHelper for CryptoDevice { + fn format_mkdir_parameters(&self, username: &str) -> [String; 3] { + [ + "mkdir".to_owned(), + "-p".to_owned(), + format!( + "/run/media/{}/{}", + username, + self.device_name + .clone() + .unwrap_or_else(|| self.uuid.clone()) + ), + ] + } + + fn format_mount_parameters(&mut self, username: &str) -> Vec<String> { + vec![ + "mount".to_owned(), + format!("/dev/mapper/{}", self.uuid), + format!( + "/run/media/{}/{}", + username, + self.device_name + .clone() + .unwrap_or_else(|| self.uuid.clone()) + ), + ] + } + + fn format_umount_parameters(&self, username: &str) -> Vec<String> { + vec![ + "umount".to_owned(), + format!( + "/run/media/{}/{}", + username, + self.device_name + .clone() + .unwrap_or_else(|| self.uuid.clone()) + ), + ] + } + + /// True if there's a mount point for this drive. + /// It's only valid if we mounted the device since it requires + /// the uuid to be in the mount point. + fn is_mounted(&self) -> bool { + self.mount_point().is_some() + } + + fn mount(&mut self, username: &str, _: &mut PasswordHolder) -> Result<bool> { + self.set_device_name()?; + // mkdir + let (success, stdout, stderr) = sudo(&self.format_mkdir_parameters(username))?; + info!("stdout: {}\nstderr: {}", stdout, stderr); + if !success { + return Ok(false); + } + // mount + let (success, stdout, stderr) = sudo(&self.format_mount_parameters(username))?; + info!("stdout: {}\nstderr: {}", stdout, stderr); + if !success { + return Ok(false); + } + // sudo -k + sudo(&["-k".to_owned()])?; + Ok(success) + } + + fn umount(&mut self, username: &str, password: &mut PasswordHolder) -> Result<bool> { + self.set_device_name()?; + // sudo + let (success, _, _) = sudo_password( + &["-S".to_owned(), "ls".to_owned(), "/root".to_owned()], + &password.sudo()?, + )?; + if !success { + return Ok(false); + } + // unmount + let (_, stdout, stderr) = sudo(&self.format_umount_parameters(username))?; + info!("stdout: {}\nstderr: {}", stdout, stderr); + + Ok(true) + } + + /// String representation of the device. + fn as_string(&self) -> Result<String> { + Ok(if let Some(mount_point) = self.mount_point() { + format!("{} -> {}", self.path, mount_point) + } else { + format!("{} - not mounted", self.path) + }) + } +} + /// Holds the device itself and its passwords. #[derive(Debug, Clone, Default)] -pub struct Device { +pub struct CryptoDeviceMounter { pub cryptdevice: CryptoDevice, pub password_holder: PasswordHolder, } -impl Device { +impl CryptoDeviceMounter { /// Reads a device from a line of text from cryptsetup. - pub fn from_line(line: &str) -> FmResult<Self> { + pub fn from_line(line: &str) -> Result<Self> { Ok(Self { cryptdevice: CryptoDevice::from_line(line)?, password_holder: PasswordHolder::default(), @@ -392,17 +299,17 @@ impl Device { /// It's a navigable content so the index follows the selection /// of the user. #[derive(Debug, Clone, Default)] -pub struct DeviceOpener { - pub content: Vec<Device>, +pub struct CryptoDeviceOpener { + pub content: Vec<CryptoDeviceMounter>, index: usize, } -impl DeviceOpener { +impl CryptoDeviceOpener { /// Updates itself from the output of cryptsetup. - pub fn update(&mut self) -> FmResult<()> { + pub fn update(&mut self) -> Result<()> { self.content = filter_crypto_devices_lines(get_devices()?, "crypto") .iter() - .map(|line| Device::from_line(line)) + .map(|line| CryptoDeviceMounter::from_line(line)) .filter_map(|r| r.ok()) .collect(); self.index = 0; @@ -420,7 +327,7 @@ impl DeviceOpener { } /// Open and mount the selected device. - pub fn mount_selected(&mut self) -> FmResult<()> { + pub fn mount_selected(&mut self) -> Result<()> { let username = current_username()?; let mut passwords = self.content[self.index].password_holder.clone(); let success = self.content[self.index] @@ -435,7 +342,7 @@ impl DeviceOpener { } /// Unmount and close the selected device. - pub fn umount_selected(&mut self) -> FmResult<()> { + pub fn umount_selected(&mut self) -> Result<()> { let username = current_username()?; let mut passwords = self.content[self.index].password_holder.clone(); let success = self.content[self.index] @@ -449,7 +356,7 @@ impl DeviceOpener { Ok(()) } - fn reset_faillock() -> FmResult<()> { + fn reset_faillock() -> Result<()> { Command::new("faillock") .arg("--user") .arg(current_username()?) @@ -461,7 +368,7 @@ impl DeviceOpener { Ok(()) } - fn drop_sudo() -> FmResult<()> { + fn drop_sudo() -> Result<()> { Command::new("sudo") .arg("-k") .stdin(Stdio::null()) @@ -482,4 +389,4 @@ impl DeviceOpener { } } -impl_selectable_content!(Device, DeviceOpener); +impl_selectable_content!(CryptoDeviceMounter, CryptoDeviceOpener); |