use crate::error::{KeyringError, Result};
use crate::Keyring;
use crate::*;
use std::collections::HashMap;
use std::fs::{File, OpenOptions, TryLockError};
use std::io::{BufReader, BufWriter, Seek};
#[cfg(unix)]
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
use std::path::{Path, PathBuf};
use std::sync::Mutex;
pub struct InsecureKeyringManagerInner {
file: File,
path: PathBuf,
secrets: HashMap<(String, String, String), String>,
}
pub struct InsecureKeyringManager {
application: String,
inner: Mutex<InsecureKeyringManagerInner>,
}
impl InsecureKeyringManager {
pub fn new(application: &str, path: &Path) -> Result<Self> {
debug!(target: "keyring::insecure", "Insecure secret storage at: {:?}", path);
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir)
.map_err(map_log_err)
.map_err(KeyringError::IoError)?;
}
let write_path = Self::get_write_path(path)?;
if write_path.exists() {
let errstr = format!("Backup file exists! Recover your insecure keyring file here or risk losing your data: {:?}", write_path);
error!(target: "keyring::insecure", "{}", errstr);
return Err(KeyringError::IoError(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
errstr,
)));
}
let mut options = OpenOptions::new();
let options = options.read(true).write(true).create(true);
#[cfg(unix)]
let options = options.mode(0o600);
let file = options
.open(path)
.map_err(map_log_err)
.map_err(KeyringError::IoError)?;
match file.try_lock() {
Ok(_) => Ok(()),
Err(TryLockError::WouldBlock) => Err(KeyringError::IoError(std::io::Error::new(
std::io::ErrorKind::WouldBlock,
"File is already locked",
))),
Err(TryLockError::Error(e)) => Err(KeyringError::IoError(std::io::Error::new(
std::io::ErrorKind::Other,
e,
))),
}?;
let out = Self {
application: application.to_owned(),
inner: Mutex::new(InsecureKeyringManagerInner {
file,
path: path.to_owned(),
secrets: HashMap::new(),
}),
};
out.load_secrets()?;
Ok(out)
}
pub fn with_keyring<F, T>(&self, service: &str, key: &str, func: F) -> Result<T>
where
F: FnOnce(&mut dyn Keyring) -> Result<T>,
{
let mut inner = self.inner.lock().unwrap();
let mut kr = InsecureKeyring::new(&mut inner.secrets, &self.application, service, key)?;
let out = func(&mut kr);
if kr.changed {
Self::save_secrets(&mut inner)?
}
out
}
fn get_appended_path(path: &Path, suffix: &str) -> Result<PathBuf> {
let mut path = path.to_owned();
let mut filename = path
.file_name()
.ok_or_else(|| KeyringError::Generic(format!("missing file name in path: {:?}", path)))
.map_err(map_log_err)?
.to_owned();
filename.push(suffix);
path.set_file_name(filename);
Ok(path)
}
fn get_write_path(path: &Path) -> Result<PathBuf> {
Self::get_appended_path(path, "~")
}
fn load_secrets(&self) -> Result<()> {
let mut inner = self.inner.lock().unwrap();
if inner
.file
.metadata()
.map_err(map_log_err)
.map_err(KeyringError::IoError)?
.len()
== 0
{
return Ok(());
}
inner
.file
.rewind()
.map_err(map_log_err)
.map_err(KeyringError::IoError)?;
let buf_file = BufReader::new(
inner
.file
.try_clone()
.map_err(map_log_err)
.map_err(KeyringError::IoError)?,
);
match ciborium::from_reader(buf_file)
.map_err(map_log_err)
.map_err(map_to_generic)
{
Ok(s) => {
inner.secrets = s;
}
Err(_) => {
warn!("Secrets file is corrupted, starting fresh.");
}
}
Ok(())
}
fn save_secrets(inner: &mut InsecureKeyringManagerInner) -> Result<()> {
debug!(target: "keyring::insecure_secrets", "Saving {} insecure secrets", inner.secrets.len());
let write_path = Self::get_write_path(&inner.path)?;
let mut options = OpenOptions::new();
let options = options.read(true).write(true).create(true).truncate(true);
#[cfg(unix)]
let options = options.mode(0o600);
let mut write_file = options
.open(&write_path)
.map_err(KeyringError::IoError)
.map_err(map_log_err)?;
write_file
.lock()
.map_err(map_log_err)
.map_err(KeyringError::IoError)?;
#[cfg(unix)]
write_file
.metadata()
.map_err(map_log_err)
.map_err(KeyringError::IoError)?
.permissions()
.set_mode(0o600);
{
let buf_write_file = BufWriter::new(
write_file
.try_clone()
.map_err(map_log_err)
.map_err(KeyringError::IoError)?,
);
ciborium::into_writer(&inner.secrets, buf_write_file)
.map_err(map_log_err)
.map_err(map_to_generic)?;
}
std::mem::swap(&mut inner.file, &mut write_file);
drop(write_file);
std::fs::rename(&write_path, &inner.path)
.map_err(map_log_err)
.map_err(KeyringError::IoError)
}
}
pub struct InsecureKeyring<'a> {
secrets: &'a mut HashMap<(String, String, String), String>,
hash_key: (String, String, String),
changed: bool,
}
impl<'a> InsecureKeyring<'a> {
pub fn new(
secrets: &'a mut HashMap<(String, String, String), String>,
application: &str,
service: &str,
key: &str,
) -> Result<InsecureKeyring<'a>> {
Ok(InsecureKeyring {
secrets,
hash_key: (application.to_owned(), service.to_owned(), key.to_owned()),
changed: false,
})
}
}
impl Keyring for InsecureKeyring<'_> {
fn set_value(&mut self, value: &str) -> Result<()> {
if let Some(old_value) = self
.secrets
.insert(self.hash_key.clone(), crate::escape(value).to_string())
{
if old_value != value {
self.changed = true;
}
} else {
self.changed = true;
}
Ok(())
}
fn get_value(&self) -> Result<String> {
crate::unescape(
self.secrets
.get(&self.hash_key)
.ok_or(KeyringError::NoPasswordFound)?,
)
.map_err(map_to_generic)
}
fn delete_value(&mut self) -> Result<()> {
if self.secrets.remove(&self.hash_key).is_none() {
return Err(KeyringError::NoPasswordFound);
}
self.changed = true;
Ok(())
}
}