diff --git a/Cargo.toml b/Cargo.toml index a71e10f..5e2ba42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,3 +57,7 @@ path = "src/main.rs" [[bin]] name = "hagrid-import" path = "src/import.rs" + +[[bin]] +name = "hagrid-delete" +path = "src/delete.rs" diff --git a/src/delete.rs b/src/delete.rs new file mode 100644 index 0000000..2533337 --- /dev/null +++ b/src/delete.rs @@ -0,0 +1,127 @@ +//! Deletes (address, key)-binding(s), and/or a key(s). + +#![feature(try_from)] + +use std::convert::TryInto; +use std::path::PathBuf; + +extern crate failure; +use failure::Fallible as Result; +extern crate structopt; +use structopt::StructOpt; + +extern crate hagrid_database as database; +use database::{Query, Database, Filesystem}; + +#[derive(Debug, StructOpt)] +#[structopt( + name = "hagrid-delete", + about = "Deletes (address, key)-binding(s), and/or a key(s)." +)] +pub struct Opt { + /// Base directory. + #[structopt(parse(from_os_str))] + base: PathBuf, + + /// E-Mail address, Fingerprint, or KeyID of the TPK to delete. + /// If a Fingerprint or KeyID is given, --all is implied. + query: String, + + /// Also delete all bindings. + #[structopt(long = "all-bindings")] + all_bindings: bool, + + /// Also delete all bindings and the key. + #[structopt(long = "all")] + all: bool, +} + +fn main() { + if let Err(e) = real_main() { + let mut cause = e.as_fail(); + eprint!("{}", cause); + while let Some(c) = cause.cause() { + eprint!(":\n {}", c); + cause = c; + } + eprintln!(); + ::std::process::exit(2); + } +} + +fn real_main() -> Result<()> { + let opt = Opt::from_args(); + let db = Filesystem::new(opt.base.canonicalize()?)?; + delete(&db, &opt.query.parse()?, opt.all_bindings, opt.all) +} + +fn delete(db: &Filesystem, query: &Query, all_bindings: bool, mut all: bool) + -> Result<()> { + match query { + Query::ByFingerprint(_) | Query::ByKeyID(_) => { + eprintln!("Fingerprint or KeyID given, deleting all bindings."); + all = true; + }, + Query::ByEmail(_) => (), + } + + let tpk = db.lookup(&query)?.ok_or_else( + || failure::format_err!("No TPK matching {:?}", query))?; + + let fp: database::types::Fingerprint = tpk.fingerprint().try_into()?; + let mut results = Vec::new(); + + // First, delete the bindings. + if all_bindings || all { + for uidb in tpk.userids() { + results.push( + (uidb.userid().to_string(), + db.unlink_email(&uidb.userid().clone().try_into()?, &fp))); + } + } else { + if let Query::ByEmail(ref email) = query { + results.push((email.to_string(), db.unlink_email(email, &fp))); + } else { + unreachable!() + } + } + + // Now delete the key(s) itself. + if all { + for skb in tpk.subkeys() { + results.push( + (skb.subkey().fingerprint().to_keyid().to_string(), + db.unlink_kid(&skb.subkey().fingerprint().try_into()?, + &fp))); + results.push( + (skb.subkey().fingerprint().to_string(), + db.unlink_fpr(&skb.subkey().fingerprint().try_into()?, + &fp))); + } + + results.push( + (tpk.fingerprint().to_keyid().to_string(), + db.unlink_kid(&tpk.fingerprint().try_into()?, + &fp))); + results.push( + (tpk.fingerprint().to_string(), + db.update(&fp, None))); + } + + let mut err = Ok(()); + for (slug, result) in results { + eprintln!("{}: {}", slug, + if let Err(ref e) = result { + e.to_string() + } else { + "Deleted".into() + }); + if err.is_ok() { + if let Err(e) = result { + err = Err(e); + } + } + } + + err +}