// main.rs
// *************************************************************************
// * Copyright (C) 2017-2020 Daniel Mueller (deso@posteo.net) *
// * *
// * This program is free software: you can redistribute it and/or modify *
// * it under the terms of the GNU General Public License as published by *
// * the Free Software Foundation, either version 3 of the License, or *
// * (at your option) any later version. *
// * *
// * This program is distributed in the hope that it will be useful, *
// * but WITHOUT ANY WARRANTY; without even the implied warranty of *
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
// * GNU General Public License for more details. *
// * *
// * You should have received a copy of the GNU General Public License *
// * along with this program. If not, see . *
// *************************************************************************
#![warn(
bad_style,
dead_code,
future_incompatible,
illegal_floating_point_literal_pattern,
improper_ctypes,
intra_doc_link_resolution_failure,
late_bound_lifetime_arguments,
missing_debug_implementations,
missing_docs,
no_mangle_generic_items,
non_shorthand_field_patterns,
nonstandard_style,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
private_in_public,
proc_macro_derive_resolution_fallback,
renamed_and_removed_lints,
rust_2018_compatibility,
rust_2018_idioms,
safe_packed_borrows,
stable_features,
trivial_bounds,
trivial_numeric_casts,
type_alias_bounds,
tyvar_behind_raw_pointer,
unconditional_recursion,
unreachable_code,
unreachable_patterns,
unstable_features,
unstable_name_collisions,
unused,
unused_comparisons,
unused_import_braces,
unused_lifetimes,
unused_qualifications,
unused_results,
where_clauses_object_safety,
while_true
)]
//! Nitrocli is a program providing a command line interface to certain
//! commands of Nitrokey Pro and Storage devices.
#[macro_use]
mod redefine;
#[macro_use]
mod arg_util;
mod args;
mod commands;
mod config;
mod pinentry;
#[cfg(test)]
mod tests;
use std::env;
use std::ffi;
use std::io;
use std::process;
const NITROCLI_ADMIN_PIN: &str = "NITROCLI_ADMIN_PIN";
const NITROCLI_USER_PIN: &str = "NITROCLI_USER_PIN";
const NITROCLI_NEW_ADMIN_PIN: &str = "NITROCLI_NEW_ADMIN_PIN";
const NITROCLI_NEW_USER_PIN: &str = "NITROCLI_NEW_USER_PIN";
const NITROCLI_PASSWORD: &str = "NITROCLI_PASSWORD";
const NITROCLI_NO_CACHE: &str = "NITROCLI_NO_CACHE";
trait Stdio {
fn stdio(&mut self) -> (&mut dyn io::Write, &mut dyn io::Write);
}
impl<'io> Stdio for RunCtx<'io> {
fn stdio(&mut self) -> (&mut dyn io::Write, &mut dyn io::Write) {
(self.stdout, self.stderr)
}
}
impl Stdio for (&mut W, &mut W)
where
W: io::Write,
{
fn stdio(&mut self) -> (&mut dyn io::Write, &mut dyn io::Write) {
(self.0, self.1)
}
}
/// A command execution context that captures additional data pertaining
/// the command execution.
#[allow(missing_debug_implementations)]
pub struct ExecCtx<'io> {
/// See `RunCtx::stdout`.
pub stdout: &'io mut dyn io::Write,
/// See `RunCtx::stderr`.
pub stderr: &'io mut dyn io::Write,
/// See `RunCtx::admin_pin`.
pub admin_pin: Option,
/// See `RunCtx::user_pin`.
pub user_pin: Option,
/// See `RunCtx::new_admin_pin`.
pub new_admin_pin: Option,
/// See `RunCtx::new_user_pin`.
pub new_user_pin: Option,
/// See `RunCtx::password`.
pub password: Option,
/// See `RunCtx::config`.
pub config: config::Config,
}
impl<'io> Stdio for ExecCtx<'io> {
fn stdio(&mut self) -> (&mut dyn io::Write, &mut dyn io::Write) {
(self.stdout, self.stderr)
}
}
/// Parse the command-line arguments and execute the selected command.
fn handle_arguments(ctx: &mut RunCtx<'_>, args: Vec) -> anyhow::Result<()> {
use structopt::StructOpt;
match args::Args::from_iter_safe(args.iter()) {
Ok(args) => {
let mut config = ctx.config;
config.update(&args);
let mut ctx = ExecCtx {
stdout: ctx.stdout,
stderr: ctx.stderr,
admin_pin: ctx.admin_pin.take(),
user_pin: ctx.user_pin.take(),
new_admin_pin: ctx.new_admin_pin.take(),
new_user_pin: ctx.new_user_pin.take(),
password: ctx.password.take(),
config,
};
args.cmd.execute(&mut ctx)
}
Err(err) => {
if err.use_stderr() {
Err(err.into())
} else {
println!(ctx, "{}", err.message)?;
Ok(())
}
}
}
}
/// The context used when running the program.
pub(crate) struct RunCtx<'io> {
/// The `Write` object used as standard output throughout the program.
pub stdout: &'io mut dyn io::Write,
/// The `Write` object used as standard error throughout the program.
pub stderr: &'io mut dyn io::Write,
/// The admin PIN, if provided through an environment variable.
pub admin_pin: Option,
/// The user PIN, if provided through an environment variable.
pub user_pin: Option,
/// The new admin PIN to set, if provided through an environment variable.
///
/// This variable is only used by commands that change the admin PIN.
pub new_admin_pin: Option,
/// The new user PIN, if provided through an environment variable.
///
/// This variable is only used by commands that change the user PIN.
pub new_user_pin: Option,
/// A password used by some commands, if provided through an environment variable.
pub password: Option,
/// The configuration, usually read from configuration files and environment
/// variables.
pub config: config::Config,
}
fn run<'ctx, 'io: 'ctx>(ctx: &'ctx mut RunCtx<'io>, args: Vec) -> i32 {
match handle_arguments(ctx, args) {
Ok(()) => 0,
Err(err) => {
let _ = eprintln!(ctx, "{:?}", err);
1
}
}
}
fn main() {
use std::io::Write;
let mut stdout = io::stdout();
let mut stderr = io::stderr();
let rc = match config::Config::load() {
Ok(mut config) => {
if env::var_os(NITROCLI_NO_CACHE).is_some() {
config.no_cache = true;
}
let args = env::args().collect::>();
let ctx = &mut RunCtx {
stdout: &mut stdout,
stderr: &mut stderr,
admin_pin: env::var_os(NITROCLI_ADMIN_PIN),
user_pin: env::var_os(NITROCLI_USER_PIN),
new_admin_pin: env::var_os(NITROCLI_NEW_ADMIN_PIN),
new_user_pin: env::var_os(NITROCLI_NEW_USER_PIN),
password: env::var_os(NITROCLI_PASSWORD),
config,
};
run(ctx, args)
}
Err(err) => {
let _ = writeln!(stderr, "{:?}", err);
1
}
};
// We exit the process the hard way below. The problem is that because
// of this, buffered IO may not be flushed. So make sure to explicitly
// flush before exiting. Note that stderr is unbuffered, alleviating
// the need for any flushing there.
// Ideally we would just make `main` return an i32 and let Rust deal
// with all of this, but the `process::Termination` functionality is
// still unstable and we have no way to convince the caller to "just
// exit" without printing additional information.
let _ = stdout.flush();
process::exit(rc);
}