diff options
| -rw-r--r-- | nitrocli/src/args.rs | 107 | ||||
| -rw-r--r-- | nitrocli/src/commands.rs | 24 | 
2 files changed, 130 insertions, 1 deletions
| diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs index 298b157..36da560 100644 --- a/nitrocli/src/args.rs +++ b/nitrocli/src/args.rs @@ -88,12 +88,14 @@ impl str::FromStr for Command {  #[derive(Debug)]  enum ConfigCommand {    Get, +  Set,  }  impl ConfigCommand {    fn execute(&self, args: Vec<String>) -> Result<()> {      match *self {        ConfigCommand::Get => config_get(args), +      ConfigCommand::Set => config_set(args),      }    }  } @@ -105,6 +107,7 @@ impl fmt::Display for ConfigCommand {        "{}",        match *self {          ConfigCommand::Get => "get", +        ConfigCommand::Set => "set",        }      )    } @@ -116,11 +119,47 @@ impl str::FromStr for ConfigCommand {    fn from_str(s: &str) -> result::Result<Self, Self::Err> {      match s {        "get" => Ok(ConfigCommand::Get), +      "set" => Ok(ConfigCommand::Set),        _ => Err(()),      }    }  } +#[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, +    } +  } +} +  #[derive(Debug)]  enum OtpCommand {    Clear, @@ -317,6 +356,74 @@ fn config_get(args: Vec<String>) -> Result<()> {    commands::config_get()  } +/// Write the Nitrokey configuration. +fn config_set(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(&parser, args)?; +  drop(parser); + +  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(numlock, capslock, scrollock, otp_pin) +} +  /// Execute an OTP subcommand.  fn otp(args: Vec<String>) -> Result<()> {    let mut subcommand = OtpCommand::Get; diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index 8ce1076..9aef2de 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -128,7 +128,7 @@ where  {    let mut data = data;    let mut retry = 3; -  let mut error_msg: Option<&str> = None; +  let mut error_msg = None;    loop {      let passphrase = match pinentry::inquire_passphrase(pin, error_msg) {        Ok(passphrase) => passphrase, @@ -304,6 +304,28 @@ pub fn config_get() -> Result<()> {    Ok(())  } +/// Write the Nitrokey configuration. +pub fn config_set( +  numlock: args::ConfigOption<u8>, +  capslock: args::ConfigOption<u8>, +  scrollock: args::ConfigOption<u8>, +  user_password: Option<bool>, +) -> Result<()> { +  let device = authenticate_admin(get_device()?)?; +  let config = device +    .get_config() +    .map_err(|err| get_error("Could not get configuration", &err))?; +  let config = nitrokey::Config { +    numlock: numlock.or(config.numlock), +    capslock: capslock.or(config.capslock), +    scrollock: scrollock.or(config.scrollock), +    user_password: user_password.unwrap_or(config.user_password), +  }; +  device +    .write_config(config) +    .map_err(|err| get_error("Could not set configuration", &err)) +} +  fn get_otp<T: GenerateOtp>(slot: u8, algorithm: args::OtpAlgorithm, device: &T) -> Result<String> {    match algorithm {      args::OtpAlgorithm::Hotp => device.get_hotp_code(slot), | 
