aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/otp-cache/src/main.rs35
1 files 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<Cache> {
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<String> {
// Attempt to prevent a "hang" of the Nitrokey by killing any scdaemon
// that could currently have the device opened itself