From ac52b71b0c23c415eede3b8fef50831ae2fe01bc Mon Sep 17 00:00:00 2001
From: Robin Krahl <robin.krahl@ireas.org>
Date: Sun, 23 Dec 2018 02:21:21 +0100
Subject: Implement the otp status subcommand

This patch introduces the `otp status` subcommand that lists all OTP
slots and their current status.  To avoid hardcoding the number of slots
per type, we iterate all slots until we get an `InvalidSlot` error
(assuming that the set of valid slots is {0, ..., n} for some n).  The
`status` command is quite slow as we have to query each slot separately.
---
 nitrocli/src/args.rs     | 22 +++++++++++++++++++++-
 nitrocli/src/commands.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs
index 5c2cbb8..ab80f3d 100644
--- a/nitrocli/src/args.rs
+++ b/nitrocli/src/args.rs
@@ -86,6 +86,7 @@ enum OtpCommand {
   Clear,
   Get,
   Set,
+  Status,
 }
 
 impl OtpCommand {
@@ -94,6 +95,7 @@ impl OtpCommand {
       OtpCommand::Clear => otp_clear(args),
       OtpCommand::Get => otp_get(args),
       OtpCommand::Set => otp_set(args),
+      OtpCommand::Status => otp_status(args),
     }
   }
 }
@@ -107,6 +109,7 @@ impl fmt::Display for OtpCommand {
         OtpCommand::Clear => "clear",
         OtpCommand::Get => "get",
         OtpCommand::Set => "set",
+        OtpCommand::Status => "status",
       }
     )
   }
@@ -120,6 +123,7 @@ impl str::FromStr for OtpCommand {
       "clear" => Ok(OtpCommand::Clear),
       "get" => Ok(OtpCommand::Get),
       "set" => Ok(OtpCommand::Set),
+      "status" => Ok(OtpCommand::Status),
       _ => Err(()),
     }
   }
@@ -249,7 +253,7 @@ fn otp(args: Vec<String>) -> Result<()> {
   let _ = parser.refer(&mut subcommand).required().add_argument(
     "subcommand",
     argparse::Store,
-    "The subcommand to execute (clear|get|set)",
+    "The subcommand to execute (clear|get|set|status)",
   );
   let _ = parser.refer(&mut subargs).add_argument(
     "arguments",
@@ -374,6 +378,22 @@ fn otp_clear(args: Vec<String>) -> Result<()> {
   commands::otp_clear(slot, algorithm)
 }
 
+/// Print the status of the OTP slots.
+fn otp_status(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(&parser, args)?;
+  drop(parser);
+
+  commands::otp_status(all)
+}
+
 /// Parse the command-line arguments and return the selected command and
 /// the remaining arguments for the command.
 fn parse_arguments(args: Vec<String>) -> Result<(Command, Vec<String>)> {
diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs
index 93e9bd3..3546e2e 100644
--- a/nitrocli/src/commands.rs
+++ b/nitrocli/src/commands.rs
@@ -333,6 +333,50 @@ pub fn otp_clear(slot: u8, algorithm: args::OtpAlgorithm) -> Result<()> {
   Ok(())
 }
 
+fn print_otp_status(
+  algorithm: args::OtpAlgorithm,
+  device: &nitrokey::DeviceWrapper,
+  all: bool,
+) -> Result<()> {
+  let mut slot: u8 = 0;
+  loop {
+    let result = match algorithm {
+      args::OtpAlgorithm::Hotp => device.get_hotp_slot_name(slot),
+      args::OtpAlgorithm::Totp => device.get_totp_slot_name(slot),
+    };
+    slot = match slot.checked_add(1) {
+      Some(slot) => slot,
+      None => {
+        return Err(Error::Error(
+          "Integer overflow when iterating OTP slots".to_string(),
+        ))
+      }
+    };
+    let name = match result {
+      Ok(name) => name,
+      Err(nitrokey::CommandError::InvalidSlot) => return Ok(()),
+      Err(nitrokey::CommandError::SlotNotProgrammed) => {
+        if all {
+          "[not programmed]".to_string()
+        } else {
+          continue;
+        }
+      }
+      Err(err) => return Err(get_error("Could not check OTP slot", &err)),
+    };
+    println!("{}\t{}\t{}", algorithm, slot - 1, name);
+  }
+}
+
+/// Print the status of the OTP slots.
+pub fn otp_status(all: bool) -> Result<()> {
+  let device = get_device()?;
+  println!("alg\tslot\tname");
+  print_otp_status(args::OtpAlgorithm::Hotp, &device, all)?;
+  print_otp_status(args::OtpAlgorithm::Totp, &device, all)?;
+  Ok(())
+}
+
 #[cfg(test)]
 mod tests {
   use super::*;
-- 
cgit v1.2.3