pub mod create;
pub mod delete;
pub mod info;
pub mod list;
pub mod read;
pub mod write;
use anyhow::{anyhow, Result};
use clap::{Args, PossibleValue, Subcommand, ValueEnum};
use log::debug;
use nuts_container::container::{Cipher, Container, OpenOptionsBuilder};
use nuts_directory::{DirectoryBackend, OpenOptions};
use rpassword::prompt_password;
use std::fs;
use std::ops::Deref;
use std::path::PathBuf;
use crate::cli::container::create::ContainerCreateArgs;
use crate::cli::container::delete::ContainerDeleteArgs;
use crate::cli::container::info::ContainerInfoArgs;
use crate::cli::container::list::ContainerListArgs;
use crate::cli::container::read::ContainerReadArgs;
use crate::cli::container::write::ContainerWriteArgs;
const AES128_GCM: &str = "aes128-gcm";
const AES128_CTR: &str = "aes128-ctr";
const NONE: &str = "none";
#[derive(Clone, Debug)]
pub struct CliCipher(Cipher);
impl PartialEq<Cipher> for CliCipher {
fn eq(&self, other: &Cipher) -> bool {
self.0 == *other
}
}
impl Deref for CliCipher {
type Target = Cipher;
fn deref(&self) -> &Cipher {
&self.0
}
}
impl ValueEnum for CliCipher {
fn value_variants<'a>() -> &'a [Self] {
&[
CliCipher(Cipher::Aes128Gcm),
CliCipher(Cipher::Aes128Ctr),
CliCipher(Cipher::None),
]
}
fn to_possible_value<'a>(&self) -> Option<PossibleValue<'a>> {
let value = match self.0 {
Cipher::None => NONE,
Cipher::Aes128Ctr => AES128_CTR,
Cipher::Aes128Gcm => AES128_GCM,
};
Some(PossibleValue::new(value))
}
}
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct ContainerArgs {
#[clap(subcommand)]
command: Option<ContainerCommand>,
}
impl ContainerArgs {
pub fn run(&self) -> Result<()> {
self.command
.as_ref()
.map_or(Ok(()), |command| command.run())
}
}
#[derive(Debug, Subcommand)]
pub enum ContainerCommand {
Create(ContainerCreateArgs),
Delete(ContainerDeleteArgs),
Info(ContainerInfoArgs),
List(ContainerListArgs),
Read(ContainerReadArgs),
Write(ContainerWriteArgs),
}
impl ContainerCommand {
pub fn run(&self) -> Result<()> {
match self {
Self::Create(args) => args.run(),
Self::Delete(args) => args.run(),
Self::Info(args) => args.run(),
Self::List(args) => args.run(),
Self::Read(args) => args.run(),
Self::Write(args) => args.run(),
}
}
}
fn tool_dir() -> Result<PathBuf> {
match home::home_dir() {
Some(dir) => {
let tool_dir = dir.join(".nuts");
debug!("tool_dir: {}", tool_dir.display());
if !tool_dir.is_dir() {
debug!("creating tool dir {}", tool_dir.display());
fs::create_dir(&tool_dir)?;
}
Ok(tool_dir)
}
None => Err(anyhow!("unable to locate home-directory")),
}
}
fn open_container(name: &str) -> Result<Container<DirectoryBackend>> {
let path = container_dir_for(name)?;
let builder = OpenOptionsBuilder::new().with_password_callback(ask_for_password);
let options = builder.build::<DirectoryBackend>()?;
Ok(Container::open(OpenOptions::for_path(path), options)?)
}
fn container_dir() -> Result<PathBuf> {
let parent = tool_dir()?;
let dir = parent.join("container.d");
debug!("container_dir: {}", dir.display());
if !dir.is_dir() {
debug!("creating container dir {}", dir.display());
fs::create_dir(&dir)?;
}
Ok(dir)
}
fn container_dir_for<S: AsRef<str>>(name: S) -> Result<PathBuf> {
let parent = container_dir()?;
let dir = parent.join(name.as_ref());
debug!("container_dir for {}: {}", name.as_ref(), dir.display());
Ok(dir)
}
pub fn ask_for_password() -> Result<Vec<u8>, String> {
let password = prompt_password("Enter a password: ").map_err(|err| err.to_string())?;
Ok(password.as_bytes().to_vec())
}