#![allow(clippy::manual_async_fn)]
#![allow(clippy::new_without_default)]
#![deny(missing_docs)]
#![deny(unsafe_code)]
#![deny(warnings)]
use lair_keystore::dependencies::*;
use lair_keystore_api::prelude::*;
use std::sync::Arc;
use structopt::StructOpt;
pub(crate) const CONFIG_N: &str = "lair-keystore-config.yaml";
mod cmd_import_seed;
mod cmd_init;
mod cmd_server;
mod cmd_url;
fn vec_to_locked(mut pass_tmp: Vec<u8>) -> LairResult<sodoken::BufRead> {
match sodoken::BufWrite::new_mem_locked(pass_tmp.len()) {
Err(e) => {
pass_tmp.fill(0);
Err(e)
}
Ok(p) => {
{
let mut lock = p.write_lock();
lock.copy_from_slice(&pass_tmp);
pass_tmp.fill(0);
}
Ok(p.to_read())
}
}
}
pub(crate) async fn read_interactive_passphrase(
prompt: &str,
) -> LairResult<sodoken::BufRead> {
let prompt = prompt.to_owned();
let pass_tmp = tokio::task::spawn_blocking(move || {
LairResult::Ok(
rpassword::prompt_password(prompt)
.map_err(one_err::OneErr::new)?
.into_bytes(),
)
})
.await
.map_err(one_err::OneErr::new)??;
vec_to_locked(pass_tmp)
}
#[allow(clippy::len_zero)]
pub(crate) async fn read_piped_passphrase() -> LairResult<sodoken::BufRead> {
let mut stdin = tokio::io::stdin();
let mut pass_tmp = Vec::new();
use tokio::io::AsyncReadExt;
stdin.read_to_end(&mut pass_tmp).await?;
if pass_tmp.len() >= 2
&& pass_tmp[pass_tmp.len() - 1] == 10
&& pass_tmp[pass_tmp.len() - 2] == 13
{
pass_tmp.pop();
pass_tmp.pop();
} else if pass_tmp.len() >= 1 && pass_tmp[pass_tmp.len() - 1] == 10 {
pass_tmp.pop();
}
vec_to_locked(pass_tmp)
}
#[derive(Debug, StructOpt)]
pub(crate) struct OptInit {
#[structopt(short = "p", long, verbatim_doc_comment)]
pub piped: bool,
}
#[derive(Debug, StructOpt)]
pub(crate) struct OptServer {
#[structopt(short = "p", long, verbatim_doc_comment)]
pub piped: bool,
}
#[derive(Debug, StructOpt)]
pub(crate) struct OptImportSeed {
#[structopt(short = "p", long, verbatim_doc_comment)]
pub piped: bool,
#[structopt(short = "d", long, verbatim_doc_comment)]
pub deep_lock: bool,
#[structopt(verbatim_doc_comment)]
pub tag: String,
#[structopt(verbatim_doc_comment)]
pub seed_bundle_base64: String,
#[structopt(short = "e", long, verbatim_doc_comment)]
pub exportable: bool,
}
#[derive(Debug, StructOpt)]
enum Cmd {
#[structopt(verbatim_doc_comment)]
Init(OptInit),
#[structopt(verbatim_doc_comment)]
Url,
#[structopt(verbatim_doc_comment)]
Server(OptServer),
#[structopt(verbatim_doc_comment)]
ImportSeed(OptImportSeed),
}
#[derive(Debug, StructOpt)]
#[structopt(about = "secret lair private keystore")]
struct Opt {
#[structopt(short = "r", long, default_value = ".", env = "LAIR_ROOT")]
lair_root: std::path::PathBuf,
#[structopt(subcommand)]
cmd: Cmd,
}
async fn get_config(
lair_root: &std::path::Path,
) -> LairResult<LairServerConfig> {
let mut config_n = lair_root.to_owned();
config_n.push(CONFIG_N);
let bytes = match tokio::fs::read(&config_n).await {
Err(e) => {
return Err(format!(
"Could not read config file {:?}, did you initialize the keystore? - {}",
config_n,
e,
).into());
}
Ok(b) => b,
};
let config = LairServerConfigInner::from_bytes(&bytes)?;
Ok(Arc::new(config))
}
async fn exec() -> LairResult<()> {
let opt = Opt::from_args();
let Opt { lair_root, cmd } = opt;
let lair_root = dunce::canonicalize(&lair_root)?;
match cmd {
Cmd::Init(opt) => cmd_init::exec(lair_root, opt).await,
Cmd::Url => {
let config = get_config(lair_root.as_path()).await?;
cmd_url::exec(config).await
}
Cmd::Server(opt) => {
let config = get_config(lair_root.as_path()).await?;
cmd_server::exec(config, opt).await
}
Cmd::ImportSeed(opt) => {
let config = get_config(lair_root.as_path()).await?;
cmd_import_seed::exec(config, opt).await
}
}
}
#[tokio::main(flavor = "multi_thread")]
async fn main() {
if let Err(e) = exec().await {
eprintln!("{}", e);
}
}