From c69380439fe2dbe0d4ced968a273e21cd474f15e Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Fri, 15 Jan 2021 10:16:10 +0100 Subject: sq: Move the store-related functionality to its own file. --- sq/src/commands/mappings.rs | 222 ++++++++++++++++++++++++++++++++++++++++++++ sq/src/commands/mod.rs | 37 +------- sq/src/sq.rs | 211 +++++++++-------------------------------- 3 files changed, 265 insertions(+), 205 deletions(-) create mode 100644 sq/src/commands/mappings.rs diff --git a/sq/src/commands/mappings.rs b/sq/src/commands/mappings.rs new file mode 100644 index 00000000..0846cb52 --- /dev/null +++ b/sq/src/commands/mappings.rs @@ -0,0 +1,222 @@ +use anyhow::Context; + +use prettytable::{Table, Cell, Row, row, cell}; + +use sequoia_openpgp as openpgp; +use openpgp::{ + Result, + cert::{ + Cert, + }, + parse::Parse, + serialize::Serialize, +}; +use sequoia_store as store; +use store::{ + Mapping, + LogIter, +}; + +use crate::{ + Config, + help_warning, + commands::dump::Convert, + open_or_stdin, + create_or_stdout, +}; + +pub fn dispatch_mapping(config: Config, m: &clap::ArgMatches) -> Result<()> { + let mapping = Mapping::open(&config.context, config.network_policy, + &config.realm_name, &config.mapping_name) + .context("Failed to open the mapping")?; + + match m.subcommand() { + ("list", Some(_)) => { + list_bindings(&mapping, &config.realm_name, &config.mapping_name)?; + }, + ("add", Some(m)) => { + let fp = m.value_of("fingerprint").unwrap().parse() + .expect("Malformed fingerprint"); + mapping.add(m.value_of("label").unwrap(), &fp)?; + }, + ("import", Some(m)) => { + let label = m.value_of("label").unwrap(); + help_warning(label); + let mut input = open_or_stdin(m.value_of("input"))?; + let cert = Cert::from_reader(&mut input)?; + mapping.import(label, &cert)?; + }, + ("export", Some(m)) => { + let cert = mapping.lookup(m.value_of("label").unwrap())?.cert()?; + let mut output = create_or_stdout(m.value_of("output"), + config.force)?; + if m.is_present("binary") { + cert.serialize(&mut output)?; + } else { + cert.armored().serialize(&mut output)?; + } + }, + ("delete", Some(m)) => { + if m.is_present("label") == m.is_present("the-mapping") { + return Err(anyhow::anyhow!( + "Please specify either a label or --the-mapping.")); + } + + if m.is_present("the-mapping") { + mapping.delete().context("Failed to delete the mapping")?; + } else { + let binding = mapping.lookup(m.value_of("label").unwrap()) + .context("Failed to get key")?; + binding.delete().context("Failed to delete the binding")?; + } + }, + ("stats", Some(m)) => { + mapping_print_stats(&mapping, + m.value_of("label").unwrap())?; + }, + ("log", Some(m)) => { + if m.is_present("label") { + let binding = mapping.lookup(m.value_of("label").unwrap()) + .context("No such key")?; + print_log(binding.log().context("Failed to get log")?, false); + } else { + print_log(mapping.log().context("Failed to get log")?, true); + } + }, + _ => unreachable!(), + } + + Ok(()) +} + +pub fn dispatch_list(config: Config, m: &clap::ArgMatches) -> Result<()> { + match m.subcommand() { + ("mappings", Some(m)) => { + let mut table = Table::new(); + table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE); + table.set_titles(row!["realm", "name", "network policy"]); + + for (realm, name, network_policy, _) + in Mapping::list(&config.context, m.value_of("prefix").unwrap_or(""))? { + table.add_row(Row::new(vec![ + Cell::new(&realm), + Cell::new(&name), + Cell::new(&format!("{:?}", network_policy)) + ])); + } + + table.printstd(); + }, + ("bindings", Some(m)) => { + for (realm, name, _, mapping) + in Mapping::list(&config.context, m.value_of("prefix").unwrap_or(""))? { + list_bindings(&mapping, &realm, &name)?; + } + }, + ("keys", Some(_)) => { + let mut table = Table::new(); + table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE); + table.set_titles(row!["fingerprint", "updated", "status"]); + + for (fingerprint, key) in store::Store::list_keys(&config.context)? { + let stats = key.stats() + .context("Failed to get key stats")?; + table.add_row(Row::new(vec![ + Cell::new(&fingerprint.to_string()), + if let Some(t) = stats.updated { + Cell::new(&t.convert().to_string()) + } else { + Cell::new("") + }, + Cell::new("") + ])); + } + + table.printstd(); + }, + ("log", Some(_)) => { + print_log(store::Store::server_log(&config.context)?, true); + }, + _ => unreachable!(), + } + + Ok(()) +} + +fn list_bindings(mapping: &Mapping, realm: &str, name: &str) + -> Result<()> { + if mapping.iter()?.count() == 0 { + println!("No label-key bindings in the \"{}/{}\" mapping.", + realm, name); + return Ok(()); + } + + println!("Realm: {:?}, mapping: {:?}:", realm, name); + + let mut table = Table::new(); + table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE); + table.set_titles(row!["label", "fingerprint"]); + for (label, fingerprint, _) in mapping.iter()? { + table.add_row(Row::new(vec![ + Cell::new(&label), + Cell::new(&fingerprint.to_string())])); + } + table.printstd(); + Ok(()) +} + +fn print_log(iter: LogIter, with_slug: bool) { + let mut table = Table::new(); + table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE); + let mut head = row!["timestamp", "message"]; + if with_slug { + head.insert_cell(1, Cell::new("slug")); + } + table.set_titles(head); + + for entry in iter { + let mut row = row![&entry.timestamp.convert().to_string(), + &entry.short()]; + if with_slug { + row.insert_cell(1, Cell::new(&entry.slug)); + } + table.add_row(row); + } + + table.printstd(); +} + +pub fn mapping_print_stats(mapping: &store::Mapping, label: &str) -> Result<()> { + fn print_stamps(st: &store::Stamps) -> Result<()> { + println!("{} messages using this key", st.count); + if let Some(t) = st.first { + println!(" First: {}", t.convert()); + } + if let Some(t) = st.last { + println!(" Last: {}", t.convert()); + } + Ok(()) + } + + fn print_stats(st: &store::Stats) -> Result<()> { + if let Some(t) = st.created { + println!(" Created: {}", t.convert()); + } + if let Some(t) = st.updated { + println!(" Updated: {}", t.convert()); + } + print!(" Encrypted "); + print_stamps(&st.encryption)?; + print!(" Verified "); + print_stamps(&st.verification)?; + Ok(()) + } + + let binding = mapping.lookup(label)?; + println!("Binding {:?}", label); + print_stats(&binding.stats().context("Failed to get stats")?)?; + let key = binding.key().context("Failed to get key")?; + println!("Key"); + print_stats(&key.stats().context("Failed to get stats")?)?; + Ok(()) +} diff --git a/sq/src/commands/mod.rs b/sq/src/commands/mod.rs index 2cd73fe4..0c4728ef 100644 --- a/sq/src/commands/mod.rs +++ b/sq/src/commands/mod.rs @@ -33,7 +33,6 @@ pub use self::decrypt::decrypt; mod sign; pub use self::sign::sign; pub mod dump; -use dump::Convert; pub use self::dump::dump; mod inspect; pub use self::inspect::inspect; @@ -41,6 +40,7 @@ pub mod key; pub mod merge_signatures; pub use self::merge_signatures::merge_signatures; pub mod certring; +pub mod mappings; /// Returns suitable signing keys from a given list of Certs. fn get_signing_keys(certs: &[openpgp::Cert], p: &dyn Policy, @@ -507,38 +507,3 @@ pub fn join(inputs: Option, output: &mut dyn io::Write) } Ok(()) } - -pub fn mapping_print_stats(mapping: &store::Mapping, label: &str) -> Result<()> { - fn print_stamps(st: &store::Stamps) -> Result<()> { - println!("{} messages using this key", st.count); - if let Some(t) = st.first { - println!(" First: {}", t.convert()); - } - if let Some(t) = st.last { - println!(" Last: {}", t.convert()); - } - Ok(()) - } - - fn print_stats(st: &store::Stats) -> Result<()> { - if let Some(t) = st.created { - println!(" Created: {}", t.convert()); - } - if let Some(t) = st.updated { - println!(" Updated: {}", t.convert()); - } - print!(" Encrypted "); - print_stamps(&st.encryption)?; - print!(" Verified "); - print_stamps(&st.verification)?; - Ok(()) - } - - let binding = mapping.lookup(label)?; - println!("Binding {:?}", label); - print_stats(&binding.stats().context("Failed to get stats")?)?; - let key = binding.key().context("Failed to get key")?; - println!("Key"); - print_stats(&key.stats().context("Failed to get stats")?)?; - Ok(()) -} diff --git a/sq/src/sq.rs b/sq/src/sq.rs index 2a6f1c65..d84c51e8 100644 --- a/sq/src/sq.rs +++ b/sq/src/sq.rs @@ -4,7 +4,6 @@ use crossterm; use crossterm::terminal; use anyhow::Context as _; -use prettytable::{Table, Cell, Row, row, cell}; use std::fs::OpenOptions; use std::io::{self, Write}; use std::path::{Path, PathBuf}; @@ -15,7 +14,7 @@ use buffered_reader::File; use sequoia_openpgp as openpgp; use sequoia_core; use sequoia_net; -use sequoia_store as store; +use sequoia_store::Mapping; use openpgp::{ Result, @@ -37,11 +36,9 @@ use crate::openpgp::policy::StandardPolicy as P; use sequoia_core::Context; use sequoia_net as net; use sequoia_net::{KeyServer, wkd}; -use store::{Mapping, LogIter}; mod sq_cli; mod commands; -use commands::dump::Convert; fn open_or_stdin(f: Option<&str>) -> Result> { match f { @@ -238,6 +235,14 @@ fn help_warning(arg: &str) { } } +pub struct Config { + force: bool, + network_policy: net::Policy, + context: sequoia_core::Context, + realm_name: String, + mapping_name: String, +} + fn main() -> Result<()> { let policy = &mut P::new(); @@ -273,6 +278,16 @@ fn main() -> Result<()> { builder = builder.home(dir); } let ctx = builder.build()?; + + let config = Config { + force, + network_policy, + context: ctx, + realm_name: realm_name.into(), + mapping_name: mapping_name.into(), + }; + + let mut rt = tokio::runtime::Builder::new() .basic_scheduler() .enable_io() @@ -291,18 +306,22 @@ fn main() -> Result<()> { let secrets = m.values_of("secret-key-file") .map(load_keys) .unwrap_or(Ok(vec![]))?; - let mut mapping = Mapping::open(&ctx, network_policy, realm_name, - mapping_name) + let mut mapping = Mapping::open(&config.context, + config.network_policy, + &config.realm_name, + &config.mapping_name) .context("Failed to open the mapping")?; - commands::decrypt(&ctx, policy, &mut mapping, + commands::decrypt(&config.context, policy, &mut mapping, &mut input, &mut output, signatures, certs, secrets, m.is_present("dump-session-key"), m.is_present("dump"), m.is_present("hex"))?; }, ("encrypt", Some(m)) => { - let mapping = Mapping::open(&ctx, network_policy, realm_name, - mapping_name) + let mapping = Mapping::open(&config.context, + config.network_policy, + &config.realm_name, + &config.mapping_name) .context("Failed to open the mapping")?; let mut recipients = m.values_of("recipients-cert-file") .map(load_certs) @@ -386,10 +405,12 @@ fn main() -> Result<()> { let certs = m.values_of("sender-cert-file") .map(load_certs) .unwrap_or(Ok(vec![]))?; - let mut mapping = Mapping::open(&ctx, network_policy, realm_name, - mapping_name) + let mut mapping = Mapping::open(&config.context, + config.network_policy, + &config.realm_name, + &config.mapping_name) .context("Failed to open the mapping")?; - commands::verify(&ctx, policy, &mut mapping, &mut input, + commands::verify(&config.context, policy, &mut mapping, &mut input, detached.as_mut().map(|r| r as &mut (dyn io::Read + Sync + Send)), &mut output, signatures, certs)?; }, @@ -482,11 +503,13 @@ fn main() -> Result<()> { let secrets = m.values_of("secret-key-file") .map(load_keys) .unwrap_or(Ok(vec![]))?; - let mut mapping = Mapping::open(&ctx, network_policy, - realm_name, mapping_name) + let mut mapping = Mapping::open(&config.context, + config.network_policy, + &config.realm_name, + &config.mapping_name) .context("Failed to open the mapping")?; commands::decrypt::decrypt_unwrap( - &ctx, policy, &mut mapping, + &config.context, policy, &mut mapping, &mut input, &mut output, secrets, m.is_present("dump-session-key"))?; output.finalize()?; @@ -585,117 +608,10 @@ fn main() -> Result<()> { _ => unreachable!(), } }, - ("mapping", Some(m)) => { - let mapping = Mapping::open(&ctx, network_policy, realm_name, - mapping_name) - .context("Failed to open the mapping")?; - - match m.subcommand() { - ("list", Some(_)) => { - list_bindings(&mapping, realm_name, mapping_name)?; - }, - ("add", Some(m)) => { - let fp = m.value_of("fingerprint").unwrap().parse() - .expect("Malformed fingerprint"); - mapping.add(m.value_of("label").unwrap(), &fp)?; - }, - ("import", Some(m)) => { - let label = m.value_of("label").unwrap(); - help_warning(label); - let mut input = open_or_stdin(m.value_of("input"))?; - let cert = Cert::from_reader(&mut input)?; - mapping.import(label, &cert)?; - }, - ("export", Some(m)) => { - let cert = mapping.lookup(m.value_of("label").unwrap())?.cert()?; - let mut output = create_or_stdout(m.value_of("output"), force)?; - if m.is_present("binary") { - cert.serialize(&mut output)?; - } else { - cert.armored().serialize(&mut output)?; - } - }, - ("delete", Some(m)) => { - if m.is_present("label") == m.is_present("the-mapping") { - eprintln!("Please specify either a label or --the-mapping."); - exit(1); - } - - if m.is_present("the-mapping") { - mapping.delete().context("Failed to delete the mapping")?; - } else { - let binding = mapping.lookup(m.value_of("label").unwrap()) - .context("Failed to get key")?; - binding.delete().context("Failed to delete the binding")?; - } - }, - ("stats", Some(m)) => { - commands::mapping_print_stats(&mapping, - m.value_of("label").unwrap())?; - }, - ("log", Some(m)) => { - if m.is_present("label") { - let binding = mapping.lookup(m.value_of("label").unwrap()) - .context("No such key")?; - print_log(binding.log().context("Failed to get log")?, false); - } else { - print_log(mapping.log().context("Failed to get log")?, true); - } - }, - _ => unreachable!(), - } - }, - ("list", Some(m)) => { - match m.subcommand() { - ("mappings", Some(m)) => { - let mut table = Table::new(); - table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE); - table.set_titles(row!["realm", "name", "network policy"]); - - for (realm, name, network_policy, _) - in Mapping::list(&ctx, m.value_of("prefix").unwrap_or(""))? { - table.add_row(Row::new(vec![ - Cell::new(&realm), - Cell::new(&name), - Cell::new(&format!("{:?}", network_policy)) - ])); - } - - table.printstd(); - }, - ("bindings", Some(m)) => { - for (realm, name, _, mapping) - in Mapping::list(&ctx, m.value_of("prefix").unwrap_or(""))? { - list_bindings(&mapping, &realm, &name)?; - } - }, - ("keys", Some(_)) => { - let mut table = Table::new(); - table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE); - table.set_titles(row!["fingerprint", "updated", "status"]); - - for (fingerprint, key) in store::Store::list_keys(&ctx)? { - let stats = key.stats() - .context("Failed to get key stats")?; - table.add_row(Row::new(vec![ - Cell::new(&fingerprint.to_string()), - if let Some(t) = stats.updated { - Cell::new(&t.convert().to_string()) - } else { - Cell::new("") - }, - Cell::new("") - ])); - } - - table.printstd(); - }, - ("log", Some(_)) => { - print_log(store::Store::server_log(&ctx)?, true); - }, - _ => unreachable!(), - } - }, + ("mapping", Some(m)) => + commands::mappings::dispatch_mapping(config, m)?, + ("list", Some(m)) => + commands::mappings::dispatch_list(config, m)?, ("key", Some(m)) => match m.subcommand() { ("generate", Some(m)) => commands::key::generate(m, force)?, ("adopt", Some(m)) => commands::key::adopt(m, policy)?, @@ -758,49 +674,6 @@ fn main() -> Result<()> { return Ok(()) } -fn list_bindings(mapping: &Mapping, realm: &str, name: &str) - -> Result<()> { - if mapping.iter()?.count() == 0 { - println!("No label-key bindings in the \"{}/{}\" mapping.", - realm, name); - return Ok(()); - } - - println!("Realm: {:?}, mapping: {:?}:", realm, name); - - let mut table = Table::new(); - table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE); - table.set_titles(row!["label", "fingerprint"]); - for (label, fingerprint, _) in mapping.iter()? { - table.add_row(Row::new(vec![ - Cell::new(&label), - Cell::new(&fingerprint.to_string())])); - } - table.printstd(); - Ok(()) -} - -fn print_log(iter: LogIter, with_slug: bool) { - let mut table = Table::new(); - table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE); - let mut head = row!["timestamp", "message"]; - if with_slug { - head.insert_cell(1, Cell::new("slug")); - } - table.set_titles(head); - - for entry in iter { - let mut row = row![&entry.timestamp.convert().to_string(), - &entry.short()]; - if with_slug { - row.insert_cell(1, Cell::new(&entry.slug)); - } - table.add_row(row); - } - - table.printstd(); -} - /// Parses the given string depicting a ISO 8601 timestamp. fn parse_iso8601(s: &str, pad_date_with: chrono::NaiveTime) -> Result> -- cgit v1.2.3