summaryrefslogtreecommitdiffstats
path: root/src/cryptsetup.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cryptsetup.rs')
-rw-r--r--src/cryptsetup.rs349
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);