use std::ffi::OsString;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use clap::builder::PossibleValuesParser;
use clap::{Args, CommandFactory, Parser, Subcommand};
use crate::etree::ParseOps;
use crate::{Error, Result, capability, config, consts, crypto, etree, output, pki};
mod cap;
mod cas_cmd;
mod chain_head_cmd;
mod init;
mod inspect;
mod list;
mod merge_cmd;
mod pipeline;
mod pki_cmd;
mod provenance_cmd;
mod smudge;
mod verify;
mod verify_chain;
pub(super) fn make_policy(name: &str) -> Box<dyn crypto::CryptoPolicy> {
match name {
"default" => Box::new(crypto::CryptoPolicyDefault {}),
"nist" => Box::new(crypto::CryptoPolicyNIST {}),
other => panic!(
"make_policy: unknown policy '{}' (callers must validate via VALID_POLICIES)",
other
),
}
}
#[derive(Parser)]
#[command(
name = "enprot",
version,
about = "Engyon Protected Text (EPT) confidentiality processor"
)]
pub struct Cli {
#[command(flatten)]
pub common: CommonArgs,
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand)]
pub enum Command {
Encrypt(EncryptSubcmd),
Decrypt(OperationSubcmd),
Store(OperationSubcmd),
Fetch(OperationSubcmd),
EncryptStore(EncryptSubcmd),
Passthrough(OperationSubcmd),
Verify(OperationSubcmd),
List(OperationSubcmd),
Completions {
#[arg(value_enum)]
shell: clap_complete::Shell,
},
Keygen(KeygenSubcmd),
Sign(SignSubcmd),
VerifySig(VerifySigSubcmd),
Fingerprint(FingerprintSubcmd),
VerifyChain(VerifyChainSubcmd),
AuditLog(AuditLogSubcmd),
Snapshot(SnapshotSubcmd),
Pin(PinSubcmd),
Capabilities,
Init(InitSubcmd),
MergeDriver(MergeDriverSubcmd),
Resolve(ResolveSubcmd),
Conflicts(ConflictsSubcmd),
Inspect(InspectSubcmd),
Cap(cap::CapArgs),
Clean(SmudgeCleanSubcmd),
Smudge(SmudgeCleanSubcmd),
Textconv(SmudgeCleanSubcmd),
Manifest(ManifestSubcmd),
Attest(AttestSubcmd),
Scm(ScmSubcmd),
Cas(cas_cmd::CasArgs),
}
#[derive(Args)]
pub struct InitSubcmd {
#[arg(long)]
pub global: bool,
#[arg(long)]
pub force: bool,
#[arg(long)]
pub git: bool,
}
#[derive(Args)]
pub struct MergeDriverSubcmd {
pub base: PathBuf,
pub ours: PathBuf,
pub theirs: PathBuf,
#[arg(value_name = "PATH")]
pub path: Option<PathBuf>,
}
#[derive(Args)]
pub struct ResolveSubcmd {
#[arg(long, short = 'm', value_name = "MODE", default_value = "interactive")]
pub mode: String,
#[arg(long = "word", value_name = "WORD:MODE", value_delimiter = ',')]
pub word: Vec<String>,
#[arg(value_name = "FILE")]
pub file: PathBuf,
}
#[derive(Args)]
pub struct ConflictsSubcmd {
#[arg(long, value_enum, default_value_t = output::OutputFormat::Text)]
pub format: output::OutputFormat,
#[arg(value_name = "FILE")]
pub file: PathBuf,
}
#[derive(Args)]
pub struct InspectSubcmd {
#[arg(long, value_enum, default_value_t = output::OutputFormat::Text)]
pub format: output::OutputFormat,
#[arg(value_name = "FILE")]
pub file: Option<PathBuf>,
}
#[derive(Args)]
pub struct SmudgeCleanSubcmd {
#[arg(short = 'w', long = "word", value_name = "WORD")]
pub word: String,
#[arg(long, value_name = "ALG")]
pub cipher: Option<String>,
#[arg(long, value_name = "ALG")]
pub pbkdf: Option<String>,
}
#[derive(Args)]
pub struct ManifestSubcmd {
#[arg(value_name = "DIR")]
pub dir: PathBuf,
#[arg(short = 'c', long, value_name = "DIR")]
pub casdir: Option<PathBuf>,
#[arg(short = 'o', long, value_name = "FILE")]
pub output: Option<PathBuf>,
}
#[derive(Args)]
pub struct AttestSubcmd {
#[arg(long, value_name = "PRIV.pem")]
pub signer: PathBuf,
#[arg(value_name = "FILE")]
pub file: PathBuf,
}
#[derive(Args)]
pub struct ScmSubcmd {
#[command(subcommand)]
pub command: ScmCommand,
#[arg(short = 'c', long, global = true)]
pub casdir: Option<PathBuf>,
}
#[derive(Subcommand)]
pub enum ScmCommand {
Init {
#[arg(value_name = "MANIFEST")]
manifest: PathBuf,
},
Add {
#[arg(value_name = "MANIFEST")]
manifest: PathBuf,
#[arg(value_name = "PATH")]
path: PathBuf,
},
Deps {
#[arg(value_name = "MANIFEST")]
manifest: PathBuf,
#[arg(value_name = "Cargo.toml")]
cargo_toml: PathBuf,
},
Attest {
#[arg(long, value_name = "PRIV.pem")]
signer: PathBuf,
#[arg(value_name = "MANIFEST")]
manifest: PathBuf,
},
Verify {
#[arg(long, value_name = "PUB.pem")]
trust_root: PathBuf,
#[arg(value_name = "MANIFEST")]
manifest: PathBuf,
},
Diff {
#[arg(value_name = "OLD")]
old: PathBuf,
#[arg(value_name = "NEW")]
new: PathBuf,
},
}
#[derive(Args)]
pub struct EncryptSubcmd {
#[command(flatten)]
pub encrypt: EncryptOpts,
#[command(flatten)]
pub output: OutputArgs,
#[arg(long = "recipient", value_name = "PUB.pem")]
pub recipients: Vec<PathBuf>,
}
#[derive(Args)]
pub struct OperationSubcmd {
#[command(flatten)]
pub output: OutputArgs,
#[arg(long = "key-file", value_name = "PRIV.pem")]
pub key_files: Vec<PathBuf>,
}
#[derive(Args)]
pub struct KeygenSubcmd {
#[arg(value_parser = clap::builder::PossibleValuesParser::new(
pki::SigAlgKind::ALL.iter().map(|k| k.name()).collect::<Vec<_>>()
))]
pub alg: String,
#[arg(long = "out-priv", value_name = "PATH")]
pub out_priv: Option<PathBuf>,
#[arg(long = "out-pub", value_name = "PATH")]
pub out_pub: Option<PathBuf>,
}
#[derive(Args)]
pub struct SignSubcmd {
#[arg(long, value_parser = clap::builder::PossibleValuesParser::new(
pki::SigAlgKind::ALL.iter().map(|k| k.name()).collect::<Vec<_>>()
))]
pub alg: String,
#[arg(long = "key-file", value_name = "PRIV.pem")]
pub key: Vec<PathBuf>,
#[arg(value_name = "FILE")]
pub input: Option<PathBuf>,
#[arg(short = 'o', long = "out", value_name = "PATH")]
pub out: Option<PathBuf>,
}
#[derive(Args)]
pub struct VerifySigSubcmd {
#[arg(long, value_parser = clap::builder::PossibleValuesParser::new(
pki::SigAlgKind::ALL.iter().map(|k| k.name()).collect::<Vec<_>>()
))]
pub alg: String,
#[arg(long = "key-file", value_name = "PUB.pem")]
pub key: Vec<PathBuf>,
#[arg(long = "sig-file", value_name = "SIG")]
pub sig: Option<PathBuf>,
#[arg(value_name = "FILE")]
pub input: Option<PathBuf>,
}
#[derive(Args)]
pub struct FingerprintSubcmd {
#[arg(value_name = "PUB.pem")]
pub key: PathBuf,
}
#[derive(Args)]
pub struct VerifyChainSubcmd {
#[arg(long = "trust-root", value_name = "PUB.pem")]
pub trust_roots: Vec<PathBuf>,
#[arg(value_name = "FILE")]
pub files: Vec<String>,
}
#[derive(Args)]
pub struct AuditLogSubcmd {
#[arg(long = "signer", value_name = "PRIV.pem")]
pub signer: PathBuf,
#[arg(value_name = "FILE")]
pub file: String,
}
#[derive(Args)]
pub struct SnapshotSubcmd {
#[arg(value_name = "FILE")]
pub file: String,
}
#[derive(Args)]
pub struct PinSubcmd {
#[arg(value_name = "EXPECTED-HASH")]
pub expected: String,
#[arg(value_name = "FILE")]
pub file: String,
}
#[derive(Args, Clone, Debug)]
pub struct CommonArgs {
#[arg(short = 'v', long, global = true)]
pub verbose: bool,
#[arg(short = 'q', long, global = true)]
pub quiet: bool,
#[arg(long, global = true, default_value_t = consts::DEFAULT_MAX_DEPTH)]
pub max_depth: usize,
#[arg(short = 'l', long, global = true, default_value = consts::DEFAULT_LEFT_SEP)]
pub left_separator: String,
#[arg(short = 'r', long, global = true, default_value = consts::DEFAULT_RIGHT_SEP)]
pub right_separator: String,
#[arg(short = 'k', long = "key", global = true, value_name = "WORD=PASSWORD", value_parser = parse_word_password)]
pub password: Vec<(String, String)>,
#[arg(short = 'c', long, global = true, value_name = "DIRECTORY", value_parser = parse_casdir)]
pub casdir: Option<PathBuf>,
#[arg(long, global = true, value_parser = PossibleValuesParser::new(consts::VALID_POLICIES.to_vec()))]
pub policy: Option<String>,
#[arg(long, global = true, value_parser = PossibleValuesParser::new(consts::VALID_POLICIES.to_vec()))]
pub defaults: Option<String>,
#[arg(long, global = true)]
pub fips: bool,
#[arg(long, global = true, value_parser = PossibleValuesParser::new(consts::LANG_SEPARATORS.iter().map(|(n, _, _)| *n).collect::<Vec<_>>()))]
pub lang: Option<String>,
#[arg(long = "pbkdf-disable-cache", global = true)]
pub pbkdf_disable_cache: bool,
#[arg(long, global = true)]
pub anchor: bool,
#[arg(long, global = true, value_name = "PRIV.pem")]
pub signer: Option<PathBuf>,
#[arg(long, global = true, value_enum, default_value_t = output::OutputFormat::Text)]
pub format: output::OutputFormat,
#[arg(long, global = true)]
pub inline: bool,
#[arg(long, global = true, value_name = "PATH")]
pub policy_file: Option<PathBuf>,
#[arg(long, global = true, default_value_t = 1)]
pub jobs: usize,
#[arg(long, global = true)]
pub dry_run: bool,
}
impl CommonArgs {
pub fn for_filter(casdir: Option<PathBuf>) -> Self {
CommonArgs {
verbose: false,
quiet: false,
max_depth: consts::DEFAULT_MAX_DEPTH,
left_separator: consts::DEFAULT_LEFT_SEP.to_string(),
right_separator: consts::DEFAULT_RIGHT_SEP.to_string(),
password: Vec::new(),
casdir,
policy: None,
defaults: None,
fips: false,
lang: None,
pbkdf_disable_cache: false,
anchor: false,
signer: None,
format: output::OutputFormat::Text,
inline: false,
policy_file: None,
jobs: 1,
dry_run: false,
}
}
}
#[derive(Args, Default, Clone, Debug)]
pub struct EncryptOpts {
#[arg(long, value_parser = PossibleValuesParser::new(consts::VALID_PBKDF_ALGS.to_vec()))]
pub pbkdf: Option<String>,
#[arg(long, value_name = "MSEC", value_parser = parse_positive_u32)]
pub pbkdf_msec: Option<u32>,
#[arg(long, value_name = "BYTES", value_parser = parse_positive_usize)]
pub pbkdf_salt_len: Option<usize>,
#[arg(long, value_name = "PARAMS", hide = true)]
pub pbkdf_params: Option<String>,
#[arg(long, value_name = "HEX", hide = true)]
pub pbkdf_salt: Option<String>,
#[arg(long, value_parser = PossibleValuesParser::new(consts::VALID_CIPHER_ALGS.to_vec()))]
pub cipher: Option<String>,
#[arg(long, value_name = "ALG", hide = true)]
pub cipher_iv: Option<String>,
}
#[derive(Args, Clone, Debug)]
pub struct OutputArgs {
#[arg(short = 'w', long = "word", value_name = "WORD", value_delimiter = ',')]
pub word: Vec<String>,
#[arg(short = 'o', long = "output", value_name = "FILE")]
pub output: Vec<String>,
#[arg(
short = 'p',
long = "prefix",
default_value = "",
allow_hyphen_values = true
)]
pub prefix: String,
#[arg(long, value_name = "DIR", value_parser = parse_output_dir, conflicts_with = "prefix")]
pub output_dir: Option<PathBuf>,
#[arg(value_name = "FILE", default_value = "-")]
pub files: Vec<String>,
}
fn load_pems(paths: &[PathBuf]) -> Result<Vec<String>> {
paths
.iter()
.map(|p| fs::read_to_string(p).map_err(Error::from))
.collect()
}
fn load_privkey_pems(paths: &[PathBuf]) -> Result<Vec<String>> {
load_pems(paths)
}
fn parse_word_password(s: &str) -> std::result::Result<(String, String), String> {
let (word, pass) = s
.split_once('=')
.ok_or_else(|| format!("Must be of the form WORD=PASSWORD, got '{}'", s))?;
if word.is_empty() || pass.is_empty() {
return Err(format!("Must be of the form WORD=PASSWORD, got '{}'", s));
}
Ok((word.to_string(), pass.to_string()))
}
fn parse_casdir(s: &str) -> std::result::Result<PathBuf, String> {
let p = PathBuf::from(s);
if p.is_dir() {
Ok(p)
} else {
Err(format!("'{}' is not a directory", s))
}
}
fn parse_output_dir(s: &str) -> std::result::Result<PathBuf, String> {
let p = PathBuf::from(s);
if p.is_dir() {
Ok(p)
} else {
Err(format!("--output-dir '{}' is not a directory", s))
}
}
fn parse_positive_u32(s: &str) -> std::result::Result<u32, String> {
let n: u32 = s
.parse()
.map_err(|_| format!("expected a number > 0, got '{}'", s))?;
if n == 0 {
return Err(format!("expected a number > 0, got '{}'", s));
}
Ok(n)
}
fn parse_positive_usize(s: &str) -> std::result::Result<usize, String> {
let n: usize = s
.parse()
.map_err(|_| format!("expected a number > 0, got '{}'", s))?;
if n == 0 {
return Err(format!("expected a number > 0, got '{}'", s));
}
Ok(n)
}
pub fn app_main<I, T>(args: I) -> Result<()>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let default_filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn"));
tracing_subscriber::fmt()
.with_env_filter(default_filter)
.with_target(false)
.try_init()
.ok();
let cli = Cli::parse_from(args);
if cli.common.verbose {
}
tracing::info!(version = env!("CARGO_PKG_VERSION"), "enprot starting");
match cli.command {
Command::Init(a) => init::run(a),
Command::MergeDriver(a) => merge_cmd::run_merge_driver(a),
Command::Resolve(a) => merge_cmd::run_resolve(a),
Command::Conflicts(a) => merge_cmd::run_conflicts(a),
Command::Inspect(a) => inspect::run(a, cli.common),
Command::Cap(args) => cap::run(args, &cli.common),
Command::Cas(args) => cas_cmd::run(args, &cli.common),
Command::Clean(a) => smudge::run(smudge::Mode::Clean, a, cli.common),
Command::Smudge(a) => smudge::run(smudge::Mode::Smudge, a, cli.common),
Command::Textconv(a) => smudge::run(smudge::Mode::Smudge, a, cli.common),
Command::Manifest(a) => provenance_cmd::run_manifest(a),
Command::Attest(a) => provenance_cmd::run_attest(a),
Command::Scm(a) => provenance_cmd::run_scm(a),
Command::Encrypt(a) => with_config(cli.common, |common| {
pipeline::run(pipeline::RunConfig {
common,
output: a.output,
op: Some((a.encrypt, Operation::Encrypt)),
recipient_pubs: load_pems(&a.recipients)?,
recipient_privs: Vec::new(),
})
}),
Command::Decrypt(a) => with_config(cli.common, |common| {
pipeline::run(pipeline::RunConfig {
common,
output: a.output,
op: Some((EncryptOpts::default(), Operation::Decrypt)),
recipient_pubs: Vec::new(),
recipient_privs: load_privkey_pems(&a.key_files)?,
})
}),
Command::Store(a) => with_config(cli.common, |common| {
pipeline::run(pipeline::RunConfig {
common,
output: a.output,
op: Some((EncryptOpts::default(), Operation::Store)),
recipient_pubs: Vec::new(),
recipient_privs: Vec::new(),
})
}),
Command::Fetch(a) => with_config(cli.common, |common| {
pipeline::run(pipeline::RunConfig {
common,
output: a.output,
op: Some((EncryptOpts::default(), Operation::Fetch)),
recipient_pubs: Vec::new(),
recipient_privs: Vec::new(),
})
}),
Command::EncryptStore(a) => with_config(cli.common, |common| {
pipeline::run(pipeline::RunConfig {
common,
output: a.output,
op: Some((a.encrypt, Operation::EncryptStore)),
recipient_pubs: load_pems(&a.recipients)?,
recipient_privs: Vec::new(),
})
}),
Command::Passthrough(a) => with_config(cli.common, |common| {
pipeline::run(pipeline::RunConfig {
common,
output: a.output,
op: None,
recipient_pubs: Vec::new(),
recipient_privs: Vec::new(),
})
}),
Command::Verify(a) => with_config(cli.common, |common| verify::run(common, a.output)),
Command::List(a) => with_config(cli.common, |common| list::run(common, a.output)),
Command::Completions { shell } => {
clap_complete::generate(shell, &mut Cli::command(), "enprot", &mut std::io::stdout());
Ok(())
}
Command::Keygen(a) => with_config(cli.common, |common| pki_cmd::keygen(common, a)),
Command::Sign(a) => with_config(cli.common, |common| pki_cmd::sign(common, a)),
Command::VerifySig(a) => with_config(cli.common, |common| pki_cmd::verify_sig(common, a)),
Command::Fingerprint(a) => pki_cmd::fingerprint(a),
Command::VerifyChain(a) => with_config(cli.common, |common| verify_chain::run(common, a)),
Command::AuditLog(a) => with_config(cli.common, |common| {
chain_head_cmd::audit_log_stream(common, a)
}),
Command::Snapshot(a) => chain_head_cmd::snapshot(a),
Command::Pin(a) => chain_head_cmd::pin(a),
Command::Capabilities => with_config(cli.common, |common| {
let policy = resolve_policy(&common)?;
let mut paops = ParseOps::new(policy)?;
apply_common(&common, &mut paops);
let caps = capability::CapabilitySet::from_paops(&paops);
let stdout = std::io::stdout();
let mut out = stdout.lock();
match common.format {
output::OutputFormat::Text => {
for c in caps.iter_sorted() {
writeln!(out, "{}", c)?;
}
}
output::OutputFormat::Json => {
let dtos = caps
.iter_sorted()
.into_iter()
.map(capability_to_dto)
.collect::<Vec<_>>();
let payload = output::CapabilitiesOutput { capabilities: dtos };
writeln!(out, "{}", output::to_json(&payload)?)?;
}
}
Ok(())
}),
}
}
fn with_config<F>(common: CommonArgs, f: F) -> Result<()>
where
F: FnOnce(CommonArgs) -> Result<()>,
{
let common = apply_config(common)?;
f(common)
}
fn apply_config(mut common: CommonArgs) -> Result<CommonArgs> {
let cfg = config::Config::load(&PathBuf::from("."))?;
if common.casdir.is_none() {
common.casdir = cfg.casdir.clone();
}
if common.policy.is_none() {
common.policy = cfg.policy.clone();
}
if common.defaults.is_none() {
common.defaults = cfg.defaults.clone();
}
if common.lang.is_none() {
common.lang = cfg.lang.clone();
}
if common.signer.is_none() {
common.signer = cfg.chain.signer.as_ref().map(PathBuf::from);
}
if cfg.chain.auto_anchor == Some(true) {
common.anchor = true;
}
if cfg.fips == Some(true) {
common.fips = true;
}
Ok(common)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Operation {
Encrypt,
Decrypt,
Store,
Fetch,
EncryptStore,
}
impl Operation {
fn label(self) -> &'static str {
match self {
Operation::Encrypt => "encrypt",
Operation::Decrypt => "decrypt",
Operation::Store => "store",
Operation::Fetch => "fetch",
Operation::EncryptStore => "encrypt-store",
}
}
}
pub(super) fn build_anchor_config(
anchor_flag: bool,
signer_path: Option<&Path>,
op_kind: Option<Operation>,
words: &[String],
) -> Result<etree::AnchorConfig> {
if !anchor_flag {
return Ok(etree::AnchorConfig::disabled());
}
let signer_path = signer_path.ok_or_else(|| Error::InvalidArg {
arg: "anchor",
reason: "--anchor requires --signer <PRIV.pem>".to_string(),
})?;
let priv_pem = fs::read_to_string(signer_path)?;
let op = op_kind
.map(|k| k.label())
.unwrap_or("passthrough")
.to_string();
Ok(etree::AnchorConfig {
enabled: true,
operation: op,
words: words.to_vec(),
signer_priv_pem: Some(priv_pem),
})
}
pub(super) fn walk_for_chains(
tree: &etree::TextTree,
out: &mut Vec<crate::ledger::AnchorHash>,
) -> Result<()> {
for node in tree {
match node {
etree::TextNode::Chain { extfields } => {
let signed = crate::ledger::SignedAnchor::from_extfields(extfields)?;
if let Ok(h) = signed.id() {
out.push(h);
}
}
etree::TextNode::BeginEnd { txt, .. } | etree::TextNode::Encrypted { txt, .. } => {
walk_for_chains(txt, out)?;
}
_ => {}
}
}
Ok(())
}
fn capability_to_dto(c: &capability::Capability) -> output::CapabilityDto {
use capability::Capability::*;
match c {
Viewer => output::CapabilityDto {
tier: "viewer",
word: None,
key_fp: None,
},
Reader => output::CapabilityDto {
tier: "reader",
word: None,
key_fp: None,
},
Decryptor(w) => output::CapabilityDto {
tier: "decryptor",
word: Some(w.to_string()),
key_fp: None,
},
Signer(fp) => output::CapabilityDto {
tier: "signer",
word: None,
key_fp: Some(fp.to_hex()),
},
Verifier(fp) => output::CapabilityDto {
tier: "verifier",
word: None,
key_fp: Some(fp.to_hex()),
},
}
}
fn resolve_policy(common: &CommonArgs) -> Result<Box<dyn crypto::CryptoPolicy>> {
let explicit_policy = common.policy.clone();
let mut policy_name = explicit_policy
.clone()
.unwrap_or_else(|| consts::DEFAULT_POLICY.to_string());
let fips = common.fips
|| (cfg!(unix)
&& match fs::read_to_string("/proc/sys/crypto/fips_enabled") {
Ok(s) => s.starts_with('1'),
Err(_) => false,
});
if fips {
if let Some(p) = explicit_policy.as_deref()
&& p != "nist"
{
return Err(Error::InvalidArg {
arg: "--policy",
reason: format!("Policy setting of '{p}' conflicts with --fips"),
});
}
policy_name = "nist".to_string();
}
Ok(make_policy(&policy_name))
}
pub(super) fn resolve_separators(common: &CommonArgs) -> (String, String) {
if let Some(ref lang) = common.lang
&& let Some((left, right)) = consts::lang_separators(lang)
{
let l = if common.left_separator == consts::DEFAULT_LEFT_SEP {
left.to_string()
} else {
common.left_separator.clone()
};
let r = if common.right_separator == consts::DEFAULT_RIGHT_SEP {
right.to_string()
} else {
common.right_separator.clone()
};
return (l, r);
}
(
common.left_separator.clone(),
common.right_separator.clone(),
)
}
fn apply_common(common: &CommonArgs, paops: &mut ParseOps) {
if let Some(dir) = common.casdir.clone() {
paops.io.set_local_casdir(dir);
} else if Path::new("cas").is_dir() {
paops.io.set_local_casdir(Path::new("cas").to_path_buf());
} else {
paops.io.set_local_casdir(Path::new(".").to_path_buf());
}
paops.io.verbose = common.verbose && !common.quiet;
paops.io.inline_data = common.inline || common.casdir.is_none();
paops.io.dry_run = common.dry_run;
paops.max_depth = common.max_depth;
let (left, right) = resolve_separators(common);
paops.separators.left = left;
paops.separators.right = right;
paops.passwords.extend(common.password.clone());
}