From b6fc41b4fee75b3c9dfe6a17d5c06ad1a61b3abe Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Tue, 6 Oct 2020 20:57:43 -0700 Subject: otp-cache: Copy OTP to clipboard with -c/--copy --- ext/otp-cache/src/main.rs | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/ext/otp-cache/src/main.rs b/ext/otp-cache/src/main.rs index c39fa2d..9bfaeb6 100644 --- a/ext/otp-cache/src/main.rs +++ b/ext/otp-cache/src/main.rs @@ -6,6 +6,7 @@ mod ext; use std::collections; +use std::ffi; use std::fs; use std::io::Write as _; use std::path; @@ -46,6 +47,10 @@ enum Command { Get { /// The name of the OTP slot to generate a OTP from name: String, + /// Whether or not to directly copy the one-time password to the + /// clipboard. + #[structopt(short, long)] + copy: bool, }, /// Lists the cached slots and their ID List, @@ -58,8 +63,15 @@ fn main() -> anyhow::Result<()> { let slots = cache.remove(&args.algorithm).unwrap_or_default(); match &args.cmd { - Command::Get { name } => match slots.iter().find(|s| &s.name == name) { - Some(slot) => print!("{}", generate_otp(&ctx, &args, slot.index)?), + Command::Get { name, copy } => match slots.iter().find(|s| &s.name == name) { + Some(slot) => { + let otp = generate_otp(&ctx, &args, slot.index)?; + if *copy { + copy_otp(otp.trim_end().as_ref())? + } else { + print!("{}", otp) + } + } None => anyhow::bail!("No OTP slot with the given name!"), }, Command::List => { @@ -153,6 +165,25 @@ fn load_cache(path: &path::Path) -> anyhow::Result { toml::from_str(&s).map_err(From::from) } +/// Copy the provided OTP to the clipboard. +fn copy_otp(otp: &ffi::OsStr) -> anyhow::Result<()> { + // TODO: Binary and arguments should be made configurable through a + // config file. + let status = process::Command::new("clipboard") + .stdin(process::Stdio::null()) + .stdout(process::Stdio::null()) + .stderr(process::Stdio::null()) + .arg("--selection=clipboard") + .arg("--revert-after=1min") + .arg(otp) + .spawn() + .context("Failed to execute clipboard")? + .wait()?; + + anyhow::ensure!(status.success(), "clipboard invocation failed"); + Ok(()) +} + fn generate_otp(ctx: &ext::Context, args: &Args, slot: u8) -> anyhow::Result { // Attempt to prevent a "hang" of the Nitrokey by killing any scdaemon // that could currently have the device opened itself -- cgit v1.2.1