#![warn(missing_docs, rust_2018_compatibility, rust_2018_idioms, unused)] //! Reads OTP configuration from a QR code and writes it to an OTP slot on a Nitrokey device. use std::fmt; use std::fs; use std::io; use std::path; use std::process; #[derive(Debug)] enum Error { IoError(io::Error), Error(String), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Error::IoError(ref err) => write!(f, "IO error: {}", err), Error::Error(ref string) => write!(f, "Error: {}", string), } } } impl From<&str> for Error { fn from(string: &str) -> Error { Error::Error(string.to_string()) } } impl From for Error { fn from(error: io::Error) -> Error { Error::IoError(error) } } #[derive(Debug)] struct Options { slot: u8, file: Option, name: Option, } fn parse_options() -> Result { let mut options = Options { slot: 0, file: None, name: None, }; let mut parser = argparse::ArgumentParser::new(); parser.set_description( "Reads OTP configuration from a QR code and writes it to an OTP slot on a Nitrokey device.", ); parser.refer(&mut options.slot).required().add_argument( "slot", argparse::Store, "The slot to write the OTP data to", ); parser.refer(&mut options.file).add_argument( "file", argparse::StoreOption, "The file to read the QR code from", ); parser.refer(&mut options.name).add_option( &["-n", "--name"], argparse::StoreOption, "The name to store in the OTP slot", ); parser.parse_args()?; drop(parser); Ok(options) } fn import_qr_code() -> Result { let mut temp = mktemp::Temp::new_file()?; let path = temp.to_path_buf(); let status = process::Command::new("import").arg(&path).status()?; if status.success() { temp.release(); Ok(path) } else { match status.code() { Some(code) => Err(Error::Error(format!( "import failed with error code {}", code ))), None => Err(Error::from("import was terminated by a signal")), } } } fn run(options: Options) -> Result<(), Error> { let path = match options.file { Some(ref file) => path::PathBuf::from(file), None => import_qr_code()?, }; if options.file.is_none() { fs::remove_file(&path)?; } Ok(()) } fn main() { let status = match parse_options() { Ok(options) => match run(options) { Ok(()) => 0, Err(err) => { println!("{}", err); 1 } }, Err(err) => err, }; process::exit(status); }