diff options
Diffstat (limited to 'src/args.rs')
-rw-r--r-- | src/args.rs | 984 |
1 files changed, 984 insertions, 0 deletions
diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..9f4cae2 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,984 @@ +// args.rs + +// ************************************************************************* +// * Copyright (C) 2018-2019 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 <http://www.gnu.org/licenses/>. * +// ************************************************************************* + +use std::ffi; +use std::io; +use std::result; +use std::str; + +use crate::commands; +use crate::error::Error; +use crate::pinentry; +use crate::RunCtx; + +type Result<T> = result::Result<T, Error>; + +/// Wraps a writer and buffers its output. +/// +/// This implementation is similar to `io::BufWriter`, but: +/// - The inner writer is only written to if `flush` is called. +/// - The buffer may grow infinitely large. +struct BufWriter<'w, W: io::Write + ?Sized> { + buf: Vec<u8>, + inner: &'w mut W, +} + +impl<'w, W: io::Write + ?Sized> BufWriter<'w, W> { + pub fn new(inner: &'w mut W) -> Self { + BufWriter { + buf: Vec::with_capacity(128), + inner, + } + } +} + +impl<'w, W: io::Write + ?Sized> io::Write for BufWriter<'w, W> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.buf.extend_from_slice(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.write_all(&self.buf)?; + self.buf.clear(); + self.inner.flush() + } +} + +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<W> 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. +pub struct ExecCtx<'io> { + pub model: Option<DeviceModel>, + pub stdout: &'io mut dyn io::Write, + pub stderr: &'io mut dyn io::Write, + pub admin_pin: Option<ffi::OsString>, + pub user_pin: Option<ffi::OsString>, + pub new_admin_pin: Option<ffi::OsString>, + pub new_user_pin: Option<ffi::OsString>, + pub password: Option<ffi::OsString>, + pub no_cache: bool, + pub verbosity: u64, +} + +impl<'io> Stdio for ExecCtx<'io> { + fn stdio(&mut self) -> (&mut dyn io::Write, &mut dyn io::Write) { + (self.stdout, self.stderr) + } +} + +/// The available Nitrokey models. +#[allow(unused_doc_comments)] +Enum! {DeviceModel, [ + Pro => "pro", + Storage => "storage", +]} + +impl DeviceModel { + pub fn as_user_facing_str(&self) -> &str { + match self { + DeviceModel::Pro => "Pro", + DeviceModel::Storage => "Storage", + } + } +} + +impl From<DeviceModel> for nitrokey::Model { + fn from(model: DeviceModel) -> nitrokey::Model { + match model { + DeviceModel::Pro => nitrokey::Model::Pro, + DeviceModel::Storage => nitrokey::Model::Storage, + } + } +} + +/// A top-level command for nitrocli. +#[allow(unused_doc_comments)] +Enum! {Command, [ + Config => ("config", config), + Encrypted => ("encrypted", encrypted), + Hidden => ("hidden", hidden), + Lock => ("lock", lock), + Otp => ("otp", otp), + Pin => ("pin", pin), + Pws => ("pws", pws), + Reset => ("reset", reset), + Status => ("status", status), + Unencrypted => ("unencrypted", unencrypted), +]} + +Enum! {ConfigCommand, [ + Get => ("get", config_get), + Set => ("set", config_set), +]} + +#[derive(Clone, Copy, Debug)] +pub enum ConfigOption<T> { + Enable(T), + Disable, + Ignore, +} + +impl<T> ConfigOption<T> { + fn try_from(disable: bool, value: Option<T>, name: &'static str) -> Result<Self> { + if disable { + if value.is_some() { + Err(Error::Error(format!( + "--{name} and --no-{name} are mutually exclusive", + name = name + ))) + } else { + Ok(ConfigOption::Disable) + } + } else { + match value { + Some(value) => Ok(ConfigOption::Enable(value)), + None => Ok(ConfigOption::Ignore), + } + } + } + + pub fn or(self, default: Option<T>) -> Option<T> { + match self { + ConfigOption::Enable(value) => Some(value), + ConfigOption::Disable => None, + ConfigOption::Ignore => default, + } + } +} + +Enum! {OtpCommand, [ + Clear => ("clear", otp_clear), + Get => ("get", otp_get), + Set => ("set", otp_set), + Status => ("status", otp_status), +]} + +Enum! {OtpAlgorithm, [ + Hotp => "hotp", + Totp => "totp", +]} + +Enum! {OtpMode, [ + SixDigits => "6", + EightDigits => "8", +]} + +impl From<OtpMode> for nitrokey::OtpMode { + fn from(mode: OtpMode) -> Self { + match mode { + OtpMode::SixDigits => nitrokey::OtpMode::SixDigits, + OtpMode::EightDigits => nitrokey::OtpMode::EightDigits, + } + } +} + +Enum! {OtpSecretFormat, [ + Ascii => "ascii", + Base32 => "base32", + Hex => "hex", +]} + +Enum! {PinCommand, [ + Clear => ("clear", pin_clear), + Set => ("set", pin_set), + Unblock => ("unblock", pin_unblock), +]} + +Enum! {PwsCommand, [ + Clear => ("clear", pws_clear), + Get => ("get", pws_get), + Set => ("set", pws_set), + Status => ("status", pws_status), +]} + +fn parse( + ctx: &mut impl Stdio, + parser: argparse::ArgumentParser<'_>, + args: Vec<String>, +) -> Result<()> { + let (stdout, stderr) = ctx.stdio(); + let result = parser + .parse(args, stdout, stderr) + .map_err(Error::ArgparseError); + drop(parser); + result +} + +/// Inquire the status of the Nitrokey. +fn status(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Prints the status of the connected Nitrokey device"); + parse(ctx, parser, args)?; + + commands::status(ctx) +} + +/// Perform a factory reset. +fn reset(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Performs a factory reset"); + parse(ctx, parser, args)?; + + commands::reset(ctx) +} + +Enum! {UnencryptedCommand, [ + Set => ("set", unencrypted_set), +]} + +Enum! {UnencryptedVolumeMode, [ + ReadWrite => "read-write", + ReadOnly => "read-only", +]} + +/// Execute an unencrypted subcommand. +fn unencrypted(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut subcommand = UnencryptedCommand::Set; + let help = cmd_help!(subcommand); + let mut subargs = vec![]; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Interacts with the device's unencrypted volume"); + let _ = + parser + .refer(&mut subcommand) + .required() + .add_argument("subcommand", argparse::Store, &help); + let _ = parser.refer(&mut subargs).add_argument( + "arguments", + argparse::List, + "The arguments for the subcommand", + ); + parser.stop_on_first_argument(true); + parse(ctx, parser, args)?; + + subargs.insert( + 0, + format!( + "{} {} {}", + crate::NITROCLI, + Command::Unencrypted, + subcommand, + ), + ); + subcommand.execute(ctx, subargs) +} + +/// Change the configuration of the unencrypted volume. +fn unencrypted_set(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut mode = UnencryptedVolumeMode::ReadWrite; + let help = format!("The mode to change to ({})", fmt_enum!(mode)); + let mut parser = argparse::ArgumentParser::new(); + parser + .set_description("Changes the configuration of the unencrypted volume on a Nitrokey Storage"); + let _ = parser + .refer(&mut mode) + .required() + .add_argument("type", argparse::Store, &help); + parse(ctx, parser, args)?; + + commands::unencrypted_set(ctx, mode) +} + +Enum! {EncryptedCommand, [ + Close => ("close", encrypted_close), + Open => ("open", encrypted_open), +]} + +/// Execute an encrypted subcommand. +fn encrypted(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut subcommand = EncryptedCommand::Open; + let help = cmd_help!(subcommand); + let mut subargs = vec![]; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Interacts with the device's encrypted volume"); + let _ = + parser + .refer(&mut subcommand) + .required() + .add_argument("subcommand", argparse::Store, &help); + let _ = parser.refer(&mut subargs).add_argument( + "arguments", + argparse::List, + "The arguments for the subcommand", + ); + parser.stop_on_first_argument(true); + parse(ctx, parser, args)?; + + subargs.insert( + 0, + format!("{} {} {}", crate::NITROCLI, Command::Encrypted, subcommand), + ); + subcommand.execute(ctx, subargs) +} + +/// Open the encrypted volume on the Nitrokey. +fn encrypted_open(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Opens the encrypted volume on a Nitrokey Storage"); + parse(ctx, parser, args)?; + + commands::encrypted_open(ctx) +} + +/// Close the previously opened encrypted volume. +fn encrypted_close(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Closes the encrypted volume on a Nitrokey Storage"); + parse(ctx, parser, args)?; + + commands::encrypted_close(ctx) +} + +Enum! {HiddenCommand, [ + Close => ("close", hidden_close), + Create => ("create", hidden_create), + Open => ("open", hidden_open), +]} + +/// Execute a hidden subcommand. +fn hidden(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut subcommand = HiddenCommand::Open; + let help = cmd_help!(subcommand); + let mut subargs = vec![]; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Interacts with the device's hidden volume"); + let _ = + parser + .refer(&mut subcommand) + .required() + .add_argument("subcommand", argparse::Store, &help); + let _ = parser.refer(&mut subargs).add_argument( + "arguments", + argparse::List, + "The arguments for the subcommand", + ); + parser.stop_on_first_argument(true); + parse(ctx, parser, args)?; + + subargs.insert( + 0, + format!("{} {} {}", crate::NITROCLI, Command::Hidden, subcommand), + ); + subcommand.execute(ctx, subargs) +} + +fn hidden_create(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut slot: u8 = 0; + let mut start: u8 = 0; + let mut end: u8 = 0; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Creates a hidden volume on a Nitrokey Storage"); + let _ = parser.refer(&mut slot).required().add_argument( + "slot", + argparse::Store, + "The hidden volume slot to use", + ); + let _ = parser.refer(&mut start).required().add_argument( + "start", + argparse::Store, + "The start location of the hidden volume as percentage of the \ + encrypted volume's size (0-99)", + ); + let _ = parser.refer(&mut end).required().add_argument( + "end", + argparse::Store, + "The end location of the hidden volume as percentage of the \ + encrypted volume's size (1-100)", + ); + parse(ctx, parser, args)?; + + commands::hidden_create(ctx, slot, start, end) +} + +fn hidden_open(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Opens a hidden volume on a Nitrokey Storage"); + parse(ctx, parser, args)?; + + commands::hidden_open(ctx) +} + +fn hidden_close(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Closes the hidden volume on a Nitrokey Storage"); + parse(ctx, parser, args)?; + + commands::hidden_close(ctx) +} + +/// Execute a config subcommand. +fn config(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut subcommand = ConfigCommand::Get; + let help = cmd_help!(subcommand); + let mut subargs = vec![]; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Reads or writes the device configuration"); + let _ = + parser + .refer(&mut subcommand) + .required() + .add_argument("subcommand", argparse::Store, &help); + let _ = parser.refer(&mut subargs).add_argument( + "arguments", + argparse::List, + "The arguments for the subcommand", + ); + parser.stop_on_first_argument(true); + parse(ctx, parser, args)?; + + subargs.insert( + 0, + format!("{} {} {}", crate::NITROCLI, Command::Config, subcommand), + ); + subcommand.execute(ctx, subargs) +} + +/// Read the Nitrokey configuration. +fn config_get(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Prints the Nitrokey configuration"); + parse(ctx, parser, args)?; + + commands::config_get(ctx) +} + +/// Write the Nitrokey configuration. +fn config_set(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut numlock = None; + let mut no_numlock = false; + let mut capslock = None; + let mut no_capslock = false; + let mut scrollock = None; + let mut no_scrollock = false; + let mut otp_pin = false; + let mut no_otp_pin = false; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Changes the Nitrokey configuration"); + let _ = parser.refer(&mut numlock).add_option( + &["-n", "--numlock"], + argparse::StoreOption, + "Set the numlock option to the given HOTP slot", + ); + let _ = parser.refer(&mut no_numlock).add_option( + &["-N", "--no-numlock"], + argparse::StoreTrue, + "Unset the numlock option", + ); + let _ = parser.refer(&mut capslock).add_option( + &["-c", "--capslock"], + argparse::StoreOption, + "Set the capslock option to the given HOTP slot", + ); + let _ = parser.refer(&mut no_capslock).add_option( + &["-C", "--no-capslock"], + argparse::StoreTrue, + "Unset the capslock option", + ); + let _ = parser.refer(&mut scrollock).add_option( + &["-s", "--scrollock"], + argparse::StoreOption, + "Set the scrollock option to the given HOTP slot", + ); + let _ = parser.refer(&mut no_scrollock).add_option( + &["-S", "--no-scrollock"], + argparse::StoreTrue, + "Unset the scrollock option", + ); + let _ = parser.refer(&mut otp_pin).add_option( + &["-o", "--otp-pin"], + argparse::StoreTrue, + "Require the user PIN to generate one-time passwords", + ); + let _ = parser.refer(&mut no_otp_pin).add_option( + &["-O", "--no-otp-pin"], + argparse::StoreTrue, + "Allow one-time password generation without PIN", + ); + parse(ctx, parser, args)?; + + let numlock = ConfigOption::try_from(no_numlock, numlock, "numlock")?; + let capslock = ConfigOption::try_from(no_capslock, capslock, "capslock")?; + let scrollock = ConfigOption::try_from(no_scrollock, scrollock, "scrollock")?; + let otp_pin = if otp_pin { + Some(true) + } else if no_otp_pin { + Some(false) + } else { + None + }; + commands::config_set(ctx, numlock, capslock, scrollock, otp_pin) +} + +/// Lock the Nitrokey. +fn lock(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Locks the connected Nitrokey device"); + parse(ctx, parser, args)?; + + commands::lock(ctx) +} + +/// Execute an OTP subcommand. +fn otp(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut subcommand = OtpCommand::Get; + let help = cmd_help!(subcommand); + let mut subargs = vec![]; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Accesses one-time passwords"); + let _ = + parser + .refer(&mut subcommand) + .required() + .add_argument("subcommand", argparse::Store, &help); + let _ = parser.refer(&mut subargs).add_argument( + "arguments", + argparse::List, + "The arguments for the subcommand", + ); + parser.stop_on_first_argument(true); + parse(ctx, parser, args)?; + + subargs.insert( + 0, + format!("{} {} {}", crate::NITROCLI, Command::Otp, subcommand), + ); + subcommand.execute(ctx, subargs) +} + +/// Generate a one-time password on the Nitrokey device. +fn otp_get(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut slot: u8 = 0; + let mut algorithm = OtpAlgorithm::Totp; + let help = format!( + "The OTP algorithm to use ({}, default: {})", + fmt_enum!(algorithm), + algorithm + ); + let mut time: Option<u64> = None; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Generates a one-time password"); + let _ = + parser + .refer(&mut slot) + .required() + .add_argument("slot", argparse::Store, "The OTP slot to use"); + let _ = parser + .refer(&mut algorithm) + .add_option(&["-a", "--algorithm"], argparse::Store, &help); + let _ = parser.refer(&mut time).add_option( + &["-t", "--time"], + argparse::StoreOption, + "The time to use for TOTP generation (Unix timestamp, default: system time)", + ); + parse(ctx, parser, args)?; + + commands::otp_get(ctx, slot, algorithm, time) +} + +/// Configure a one-time password slot on the Nitrokey device. +pub fn otp_set(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut slot: u8 = 0; + let mut algorithm = OtpAlgorithm::Totp; + let algo_help = format!( + "The OTP algorithm to use ({}, default: {})", + fmt_enum!(algorithm), + algorithm + ); + let mut name = "".to_owned(); + let mut secret = "".to_owned(); + let mut digits = OtpMode::SixDigits; + let mut counter: u64 = 0; + let mut time_window: u16 = 30; + let mut secret_format = OtpSecretFormat::Hex; + let fmt_help = format!( + "The format of the secret ({}, default: {})", + fmt_enum!(OtpSecretFormat::all_variants()), + secret_format, + ); + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Configures a one-time password slot"); + let _ = + parser + .refer(&mut slot) + .required() + .add_argument("slot", argparse::Store, "The OTP slot to use"); + let _ = + parser + .refer(&mut algorithm) + .add_option(&["-a", "--algorithm"], argparse::Store, &algo_help); + let _ = parser.refer(&mut name).required().add_argument( + "name", + argparse::Store, + "The name of the slot", + ); + let _ = parser.refer(&mut secret).required().add_argument( + "secret", + argparse::Store, + "The secret to store on the slot as a hexadecimal string (unless overwritten by --format)", + ); + let _ = parser.refer(&mut digits).add_option( + &["-d", "--digits"], + argparse::Store, + "The number of digits to use for the one-time password (6 or 8, default: 6)", + ); + let _ = parser.refer(&mut counter).add_option( + &["-c", "--counter"], + argparse::Store, + "The counter value for HOTP (default: 0)", + ); + let _ = parser.refer(&mut time_window).add_option( + &["-t", "--time-window"], + argparse::Store, + "The time window for TOTP (default: 30)", + ); + let _ = + parser + .refer(&mut secret_format) + .add_option(&["-f", "--format"], argparse::Store, &fmt_help); + parse(ctx, parser, args)?; + + let data = nitrokey::OtpSlotData { + number: slot, + name, + secret, + mode: nitrokey::OtpMode::from(digits), + use_enter: false, + token_id: None, + }; + commands::otp_set(ctx, data, algorithm, counter, time_window, secret_format) +} + +/// Clear an OTP slot. +fn otp_clear(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut slot: u8 = 0; + let mut algorithm = OtpAlgorithm::Totp; + let help = format!( + "The OTP algorithm to use ({}, default: {})", + fmt_enum!(algorithm), + algorithm + ); + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Clears a one-time password slot"); + let _ = parser.refer(&mut slot).required().add_argument( + "slot", + argparse::Store, + "The OTP slot to clear", + ); + let _ = parser + .refer(&mut algorithm) + .add_option(&["-a", "--algorithm"], argparse::Store, &help); + parse(ctx, parser, args)?; + + commands::otp_clear(ctx, slot, algorithm) +} + +/// Print the status of the OTP slots. +fn otp_status(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut all = false; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Prints the status of the OTP slots"); + let _ = parser.refer(&mut all).add_option( + &["-a", "--all"], + argparse::StoreTrue, + "Show slots that are not programmed", + ); + parse(ctx, parser, args)?; + + commands::otp_status(ctx, all) +} + +/// Execute a PIN subcommand. +fn pin(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut subcommand = PinCommand::Clear; + let help = cmd_help!(subcommand); + let mut subargs = vec![]; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Manages the Nitrokey PINs"); + let _ = + parser + .refer(&mut subcommand) + .required() + .add_argument("subcommand", argparse::Store, &help); + let _ = parser.refer(&mut subargs).add_argument( + "arguments", + argparse::List, + "The arguments for the subcommand", + ); + parser.stop_on_first_argument(true); + parse(ctx, parser, args)?; + + subargs.insert( + 0, + format!("{} {} {}", crate::NITROCLI, Command::Pin, subcommand), + ); + subcommand.execute(ctx, subargs) +} + +/// Clear the PIN as cached by various other commands. +fn pin_clear(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Clears the cached PINs"); + parse(ctx, parser, args)?; + + commands::pin_clear(ctx) +} + +/// Change a PIN. +fn pin_set(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut pintype = pinentry::PinType::User; + let help = format!("The PIN type to change ({})", fmt_enum!(pintype)); + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Changes a PIN"); + let _ = parser + .refer(&mut pintype) + .required() + .add_argument("type", argparse::Store, &help); + parse(ctx, parser, args)?; + + commands::pin_set(ctx, pintype) +} + +/// Unblock and reset the user PIN. +fn pin_unblock(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Unblocks and resets the user PIN"); + parse(ctx, parser, args)?; + + commands::pin_unblock(ctx) +} + +/// Execute a PWS subcommand. +fn pws(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut subcommand = PwsCommand::Get; + let mut subargs = vec![]; + let help = cmd_help!(subcommand); + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Accesses the password safe"); + let _ = + parser + .refer(&mut subcommand) + .required() + .add_argument("subcommand", argparse::Store, &help); + let _ = parser.refer(&mut subargs).add_argument( + "arguments", + argparse::List, + "The arguments for the subcommand", + ); + parser.stop_on_first_argument(true); + parse(ctx, parser, args)?; + + subargs.insert( + 0, + format!("{} {} {}", crate::NITROCLI, Command::Pws, subcommand), + ); + subcommand.execute(ctx, subargs) +} + +/// Access a slot of the password safe on the Nitrokey. +fn pws_get(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut slot: u8 = 0; + let mut name = false; + let mut login = false; + let mut password = false; + let mut quiet = false; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Reads a password safe slot"); + let _ = parser.refer(&mut slot).required().add_argument( + "slot", + argparse::Store, + "The PWS slot to read", + ); + let _ = parser.refer(&mut name).add_option( + &["-n", "--name"], + argparse::StoreTrue, + "Show the name stored on the slot", + ); + let _ = parser.refer(&mut login).add_option( + &["-l", "--login"], + argparse::StoreTrue, + "Show the login stored on the slot", + ); + let _ = parser.refer(&mut password).add_option( + &["-p", "--password"], + argparse::StoreTrue, + "Show the password stored on the slot", + ); + let _ = parser.refer(&mut quiet).add_option( + &["-q", "--quiet"], + argparse::StoreTrue, + "Print the stored data without description", + ); + parse(ctx, parser, args)?; + + commands::pws_get(ctx, slot, name, login, password, quiet) +} + +/// Set a slot of the password safe on the Nitrokey. +fn pws_set(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut slot: u8 = 0; + let mut name = String::new(); + let mut login = String::new(); + let mut password = String::new(); + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Writes a password safe slot"); + let _ = parser.refer(&mut slot).required().add_argument( + "slot", + argparse::Store, + "The PWS slot to write", + ); + let _ = parser.refer(&mut name).required().add_argument( + "name", + argparse::Store, + "The name to store on the slot", + ); + let _ = parser.refer(&mut login).required().add_argument( + "login", + argparse::Store, + "The login to store on the slot", + ); + let _ = parser.refer(&mut password).required().add_argument( + "password", + argparse::Store, + "The password to store on the slot", + ); + parse(ctx, parser, args)?; + + commands::pws_set(ctx, slot, &name, &login, &password) +} + +/// Clear a PWS slot. +fn pws_clear(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut slot: u8 = 0; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Clears a password safe slot"); + let _ = parser.refer(&mut slot).required().add_argument( + "slot", + argparse::Store, + "The PWS slot to clear", + ); + parse(ctx, parser, args)?; + + commands::pws_clear(ctx, slot) +} + +/// Print the status of the PWS slots. +fn pws_status(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> { + let mut all = false; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Prints the status of the PWS slots"); + let _ = parser.refer(&mut all).add_option( + &["-a", "--all"], + argparse::StoreTrue, + "Show slots that are not programmed", + ); + parse(ctx, parser, args)?; + + commands::pws_status(ctx, all) +} + +/// Parse the command-line arguments and execute the selected command. +pub(crate) fn handle_arguments(ctx: &mut RunCtx<'_>, args: Vec<String>) -> Result<()> { + use std::io::Write; + + let mut version = false; + let mut model: Option<DeviceModel> = None; + let model_help = format!( + "Select the device model to connect to ({})", + fmt_enum!(DeviceModel::all_variants()) + ); + let mut verbosity = 0; + let mut command = Command::Status; + let cmd_help = cmd_help!(command); + let mut subargs = vec![]; + let mut parser = argparse::ArgumentParser::new(); + let _ = parser.refer(&mut version).add_option( + &["-V", "--version"], + argparse::StoreTrue, + "Print version information and exit", + ); + let _ = parser.refer(&mut verbosity).add_option( + &["-v", "--verbose"], + argparse::IncrBy::<u64>(1), + "Increase the log level (can be supplied multiple times)", + ); + let _ = + parser + .refer(&mut model) + .add_option(&["-m", "--model"], argparse::StoreOption, &model_help); + parser.set_description("Provides access to a Nitrokey device"); + let _ = parser + .refer(&mut command) + .required() + .add_argument("command", argparse::Store, &cmd_help); + let _ = parser.refer(&mut subargs).add_argument( + "arguments", + argparse::List, + "The arguments for the command", + ); + parser.stop_on_first_argument(true); + + let mut stdout_buf = BufWriter::new(ctx.stdout); + let mut stderr_buf = BufWriter::new(ctx.stderr); + let mut stdio_buf = (&mut stdout_buf, &mut stderr_buf); + let result = parse(&mut stdio_buf, parser, args); + + if version { + println!(ctx, "{} {}", crate::NITROCLI, env!("CARGO_PKG_VERSION"))?; + Ok(()) + } else { + stdout_buf.flush()?; + stderr_buf.flush()?; + + result?; + subargs.insert(0, format!("{} {}", crate::NITROCLI, command)); + + let mut ctx = ExecCtx { + model, + 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(), + no_cache: ctx.no_cache, + verbosity, + }; + command.execute(&mut ctx, subargs) + } +} |