From f65b65bb24960a75cf1f900819c4005e7729e834 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sat, 11 Apr 2020 18:53:45 -0400 Subject: refactor client code --- src/bin/rbw.rs | 328 ------------------------------------------------ src/bin/rbw/actions.rs | 60 +++++++++ src/bin/rbw/commands.rs | 141 +++++++++++++++++++++ src/bin/rbw/main.rs | 107 ++++++++++++++++ src/bin/rbw/sock.rs | 29 +++++ 5 files changed, 337 insertions(+), 328 deletions(-) delete mode 100644 src/bin/rbw.rs create mode 100644 src/bin/rbw/actions.rs create mode 100644 src/bin/rbw/commands.rs create mode 100644 src/bin/rbw/main.rs create mode 100644 src/bin/rbw/sock.rs (limited to 'src/bin') diff --git a/src/bin/rbw.rs b/src/bin/rbw.rs deleted file mode 100644 index fb31f10..0000000 --- a/src/bin/rbw.rs +++ /dev/null @@ -1,328 +0,0 @@ -use std::io::{BufRead as _, Write as _}; - -fn ensure_agent() { - let agent_path = std::env::var("RBW_AGENT"); - let agent_path = agent_path - .as_ref() - .map(|s| s.as_str()) - .unwrap_or("rbw-agent"); - let status = std::process::Command::new(agent_path).status().unwrap(); - if !status.success() { - if let Some(code) = status.code() { - if code != 23 { - panic!("failed to run agent: {}", status); - } - } - } -} - -fn connect() -> std::os::unix::net::UnixStream { - std::os::unix::net::UnixStream::connect( - rbw::dirs::runtime_dir().join("socket"), - ) - .unwrap() -} - -fn send( - sock: &mut std::os::unix::net::UnixStream, - msg: &rbw::agent::Request, -) { - sock.write_all(serde_json::to_string(msg).unwrap().as_bytes()) - .unwrap(); - sock.write_all(b"\n").unwrap(); -} - -fn recv(sock: &mut std::os::unix::net::UnixStream) -> rbw::agent::Response { - let mut buf = std::io::BufReader::new(sock); - let mut line = String::new(); - buf.read_line(&mut line).unwrap(); - serde_json::from_str(&line).unwrap() -} - -fn single_action(action: rbw::agent::Action, desc: &str) { - let mut sock = connect(); - - send( - &mut sock, - &rbw::agent::Request { - tty: std::env::var("TTY").ok(), - action, - }, - ); - - let res = recv(&mut sock); - match res { - rbw::agent::Response::Ack => (), - rbw::agent::Response::Error { error } => { - panic!("failed to {}: {}", desc, error) - } - _ => panic!("unexpected message: {:?}", res), - } -} - -fn decrypt(cipherstring: &str) -> String { - let mut sock = connect(); - send( - &mut sock, - &rbw::agent::Request { - tty: std::env::var("TTY").ok(), - action: rbw::agent::Action::Decrypt { - cipherstring: cipherstring.to_string(), - }, - }, - ); - - let res = recv(&mut sock); - match res { - rbw::agent::Response::Decrypt { plaintext } => plaintext, - rbw::agent::Response::Error { error } => { - panic!("failed to decrypt: {}", error) - } - _ => panic!("unexpected message: {:?}", res), - } -} - -fn config_show() { - let config = rbw::config::Config::load().unwrap(); - serde_json::to_writer_pretty(std::io::stdout(), &config).unwrap(); - println!(); -} - -fn config_set(key: &str, value: &str) { - let mut config = rbw::config::Config::load() - .unwrap_or_else(|_| rbw::config::Config::new()); - match key { - "email" => config.email = Some(value.to_string()), - "base_url" => config.base_url = Some(value.to_string()), - "identity_url" => config.identity_url = Some(value.to_string()), - "lock_timeout" => config.lock_timeout = value.parse().unwrap(), - _ => unimplemented!(), - } - config.save().unwrap(); -} - -fn login() { - ensure_agent(); - - single_action(rbw::agent::Action::Login, "login"); -} - -fn unlock() { - ensure_agent(); - - single_action(rbw::agent::Action::Login, "login"); - single_action(rbw::agent::Action::Unlock, "unlock"); -} - -fn sync() { - ensure_agent(); - - single_action(rbw::agent::Action::Login, "login"); - single_action(rbw::agent::Action::Sync, "sync"); -} - -fn list() { - ensure_agent(); - - single_action(rbw::agent::Action::Login, "login"); - single_action(rbw::agent::Action::Unlock, "unlock"); - - let email = config_email(); - let db = rbw::db::Db::load(&email).unwrap_or_else(|_| rbw::db::Db::new()); - for cipher in db.ciphers { - println!("{}", decrypt(&cipher.name)); - } -} - -fn get(name: &str, user: Option<&str>) { - ensure_agent(); - - single_action(rbw::agent::Action::Login, "login"); - single_action(rbw::agent::Action::Unlock, "unlock"); - - let email = config_email(); - let db = rbw::db::Db::load(&email).unwrap_or_else(|_| rbw::db::Db::new()); - for cipher in db.ciphers { - let cipher_name = decrypt(&cipher.name); - if name == cipher_name { - let cipher_user = decrypt(&cipher.login.username); - if let Some(user) = user { - if user == cipher_user { - let pass = decrypt(&cipher.login.password); - println!("{}", pass); - return; - } - } else { - let pass = decrypt(&cipher.login.password); - println!("{}", pass); - return; - } - } - } -} - -fn add() { - ensure_agent(); - - todo!() -} - -fn generate( - name: Option<&str>, - user: Option<&str>, - len: usize, - ty: rbw::pwgen::Type, -) { - let pw = rbw::pwgen::pwgen(ty, len); - println!("{}", std::str::from_utf8(pw.data()).unwrap()); - - if name.is_some() && user.is_some() { - ensure_agent(); - todo!(); - } -} - -fn edit() { - ensure_agent(); - - todo!() -} - -fn remove() { - ensure_agent(); - - todo!() -} - -fn lock() { - ensure_agent(); - - single_action(rbw::agent::Action::Lock, "lock"); -} - -fn purge() { - stop_agent(); - - let email = config_email(); - rbw::db::Db::remove(&email).unwrap(); -} - -fn stop_agent() { - let mut sock = connect(); - send( - &mut sock, - &rbw::agent::Request { - tty: std::env::var("TTY").ok(), - action: rbw::agent::Action::Quit, - }, - ); -} - -fn config_email() -> String { - let config = rbw::config::Config::load().unwrap(); - config.email.unwrap() -} - -fn main() { - let matches = clap::App::new("rbw") - .about("unofficial bitwarden cli") - .author(clap::crate_authors!()) - .version(clap::crate_version!()) - .subcommand( - clap::SubCommand::with_name("config") - .subcommand(clap::SubCommand::with_name("show")) - .subcommand( - clap::SubCommand::with_name("set") - .arg(clap::Arg::with_name("key").required(true)) - .arg(clap::Arg::with_name("value").required(true)), - ), - ) - .subcommand(clap::SubCommand::with_name("login")) - .subcommand(clap::SubCommand::with_name("unlock")) - .subcommand(clap::SubCommand::with_name("sync")) - .subcommand(clap::SubCommand::with_name("list")) - .subcommand( - clap::SubCommand::with_name("get") - .arg(clap::Arg::with_name("name").required(true)) - .arg(clap::Arg::with_name("user")), - ) - .subcommand(clap::SubCommand::with_name("add")) - .subcommand( - clap::SubCommand::with_name("generate") - .arg(clap::Arg::with_name("len").required(true)) - .arg(clap::Arg::with_name("name")) - .arg(clap::Arg::with_name("user")) - .arg(clap::Arg::with_name("no-symbols").long("no-symbols")) - .arg( - clap::Arg::with_name("only-numbers").long("only-numbers"), - ) - .arg( - clap::Arg::with_name("nonconfusables") - .long("nonconfusables"), - ) - .arg(clap::Arg::with_name("diceware").long("diceware")) - .group(clap::ArgGroup::with_name("password-type").args(&[ - "no-symbols", - "only-numbers", - "nonconfusables", - "diceware", - ])), - ) - .subcommand(clap::SubCommand::with_name("edit")) - .subcommand(clap::SubCommand::with_name("remove")) - .subcommand(clap::SubCommand::with_name("lock")) - .subcommand(clap::SubCommand::with_name("purge")) - .subcommand(clap::SubCommand::with_name("stop-agent")) - .get_matches(); - - match matches.subcommand() { - ("config", Some(smatches)) => match smatches.subcommand() { - ("show", Some(_)) => config_show(), - ("set", Some(ssmatches)) => config_set( - ssmatches.value_of("key").unwrap(), - ssmatches.value_of("value").unwrap(), - ), - _ => { - eprintln!("{}", smatches.usage()); - std::process::exit(1); - } - }, - ("login", Some(_)) => login(), - ("unlock", Some(_)) => unlock(), - ("sync", Some(_)) => sync(), - ("list", Some(_)) => list(), - ("get", Some(smatches)) => get( - smatches.value_of("name").unwrap(), - smatches.value_of("user"), - ), - ("add", Some(_)) => add(), - ("generate", Some(smatches)) => { - let ty = if smatches.is_present("no-symbols") { - rbw::pwgen::Type::NoSymbols - } else if smatches.is_present("only-numbers") { - rbw::pwgen::Type::Numbers - } else if smatches.is_present("nonconfusables") { - rbw::pwgen::Type::NonConfusables - } else if smatches.is_present("diceware") { - rbw::pwgen::Type::Diceware - } else { - rbw::pwgen::Type::AllChars - }; - generate( - smatches.value_of("name"), - smatches.value_of("user"), - smatches.value_of("len").unwrap().parse().unwrap(), - ty, - ); - } - ("edit", Some(_)) => edit(), - ("remove", Some(_)) => remove(), - ("lock", Some(_)) => lock(), - ("purge", Some(_)) => purge(), - ("stop-agent", Some(_)) => stop_agent(), - _ => { - eprintln!("{}", matches.usage()); - std::process::exit(1); - } - } -} diff --git a/src/bin/rbw/actions.rs b/src/bin/rbw/actions.rs new file mode 100644 index 0000000..cca2cf1 --- /dev/null +++ b/src/bin/rbw/actions.rs @@ -0,0 +1,60 @@ +pub fn login() { + simple_action(rbw::agent::Action::Login, "login"); +} + +pub fn unlock() { + simple_action(rbw::agent::Action::Unlock, "unlock"); +} + +pub fn sync() { + simple_action(rbw::agent::Action::Sync, "sync"); +} + +pub fn lock() { + simple_action(rbw::agent::Action::Lock, "lock"); +} + +pub fn quit() { + let mut sock = crate::sock::Sock::connect(); + sock.send(&rbw::agent::Request { + tty: std::env::var("TTY").ok(), + action: rbw::agent::Action::Quit, + }); +} + +pub fn decrypt(cipherstring: &str) -> String { + let mut sock = crate::sock::Sock::connect(); + sock.send(&rbw::agent::Request { + tty: std::env::var("TTY").ok(), + action: rbw::agent::Action::Decrypt { + cipherstring: cipherstring.to_string(), + }, + }); + + let res = sock.recv(); + match res { + rbw::agent::Response::Decrypt { plaintext } => plaintext, + rbw::agent::Response::Error { error } => { + panic!("failed to decrypt: {}", error) + } + _ => panic!("unexpected message: {:?}", res), + } +} + +fn simple_action(action: rbw::agent::Action, desc: &str) { + let mut sock = crate::sock::Sock::connect(); + + sock.send(&rbw::agent::Request { + tty: std::env::var("TTY").ok(), + action, + }); + + let res = sock.recv(); + match res { + rbw::agent::Response::Ack => (), + rbw::agent::Response::Error { error } => { + panic!("failed to {}: {}", desc, error) + } + _ => panic!("unexpected message: {:?}", res), + } +} diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs new file mode 100644 index 0000000..b76658c --- /dev/null +++ b/src/bin/rbw/commands.rs @@ -0,0 +1,141 @@ +pub fn config_show() { + let config = rbw::config::Config::load().unwrap(); + serde_json::to_writer_pretty(std::io::stdout(), &config).unwrap(); + println!(); +} + +pub fn config_set(key: &str, value: &str) { + let mut config = rbw::config::Config::load() + .unwrap_or_else(|_| rbw::config::Config::new()); + match key { + "email" => config.email = Some(value.to_string()), + "base_url" => config.base_url = Some(value.to_string()), + "identity_url" => config.identity_url = Some(value.to_string()), + "lock_timeout" => config.lock_timeout = value.parse().unwrap(), + _ => unimplemented!(), + } + config.save().unwrap(); +} + +pub fn login() { + ensure_agent(); + crate::actions::login(); +} + +pub fn unlock() { + ensure_agent(); + crate::actions::login(); + crate::actions::unlock(); +} + +pub fn sync() { + ensure_agent(); + crate::actions::login(); + crate::actions::sync(); +} + +pub fn list() { + unlock(); + + let email = config_email(); + let db = rbw::db::Db::load(&email).unwrap_or_else(|_| rbw::db::Db::new()); + for cipher in db.ciphers { + println!("{}", crate::actions::decrypt(&cipher.name)); + } +} + +pub fn get(name: &str, user: Option<&str>) { + unlock(); + + let email = config_email(); + let db = rbw::db::Db::load(&email).unwrap_or_else(|_| rbw::db::Db::new()); + for cipher in db.ciphers { + let cipher_name = crate::actions::decrypt(&cipher.name); + if name == cipher_name { + let cipher_user = crate::actions::decrypt(&cipher.login.username); + if let Some(user) = user { + if user == cipher_user { + let pass = + crate::actions::decrypt(&cipher.login.password); + println!("{}", pass); + return; + } + } else { + let pass = crate::actions::decrypt(&cipher.login.password); + println!("{}", pass); + return; + } + } + } +} + +pub fn add() { + unlock(); + + todo!() +} + +pub fn generate( + name: Option<&str>, + user: Option<&str>, + len: usize, + ty: rbw::pwgen::Type, +) { + let pw = rbw::pwgen::pwgen(ty, len); + println!("{}", std::str::from_utf8(pw.data()).unwrap()); + + if name.is_some() && user.is_some() { + unlock(); + + todo!(); + } +} + +pub fn edit() { + unlock(); + + todo!() +} + +pub fn remove() { + unlock(); + + todo!() +} + +pub fn lock() { + ensure_agent(); + crate::actions::lock(); +} + +pub fn purge() { + stop_agent(); + + let email = config_email(); + rbw::db::Db::remove(&email).unwrap(); +} + +pub fn stop_agent() { + crate::actions::quit(); +} + +fn ensure_agent() { + let agent_path = std::env::var("RBW_AGENT"); + let agent_path = agent_path + .as_ref() + .map(|s| s.as_str()) + .unwrap_or("rbw-agent"); + let status = std::process::Command::new(agent_path).status().unwrap(); + if !status.success() { + if let Some(code) = status.code() { + if code != 23 { + panic!("failed to run agent: {}", status); + } + } + } +} + +fn config_email() -> String { + let config = rbw::config::Config::load().unwrap(); + config.email.unwrap() +} diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs new file mode 100644 index 0000000..065d143 --- /dev/null +++ b/src/bin/rbw/main.rs @@ -0,0 +1,107 @@ +mod actions; +mod commands; +mod sock; + +fn main() { + let matches = clap::App::new("rbw") + .about("unofficial bitwarden cli") + .author(clap::crate_authors!()) + .version(clap::crate_version!()) + .subcommand( + clap::SubCommand::with_name("config") + .subcommand(clap::SubCommand::with_name("show")) + .subcommand( + clap::SubCommand::with_name("set") + .arg(clap::Arg::with_name("key").required(true)) + .arg(clap::Arg::with_name("value").required(true)), + ), + ) + .subcommand(clap::SubCommand::with_name("login")) + .subcommand(clap::SubCommand::with_name("unlock")) + .subcommand(clap::SubCommand::with_name("sync")) + .subcommand(clap::SubCommand::with_name("list")) + .subcommand( + clap::SubCommand::with_name("get") + .arg(clap::Arg::with_name("name").required(true)) + .arg(clap::Arg::with_name("user")), + ) + .subcommand(clap::SubCommand::with_name("add")) + .subcommand( + clap::SubCommand::with_name("generate") + .arg(clap::Arg::with_name("len").required(true)) + .arg(clap::Arg::with_name("name")) + .arg(clap::Arg::with_name("user")) + .arg(clap::Arg::with_name("no-symbols").long("no-symbols")) + .arg( + clap::Arg::with_name("only-numbers").long("only-numbers"), + ) + .arg( + clap::Arg::with_name("nonconfusables") + .long("nonconfusables"), + ) + .arg(clap::Arg::with_name("diceware").long("diceware")) + .group(clap::ArgGroup::with_name("password-type").args(&[ + "no-symbols", + "only-numbers", + "nonconfusables", + "diceware", + ])), + ) + .subcommand(clap::SubCommand::with_name("edit")) + .subcommand(clap::SubCommand::with_name("remove")) + .subcommand(clap::SubCommand::with_name("lock")) + .subcommand(clap::SubCommand::with_name("purge")) + .subcommand(clap::SubCommand::with_name("stop-agent")) + .get_matches(); + + match matches.subcommand() { + ("config", Some(smatches)) => match smatches.subcommand() { + ("show", Some(_)) => commands::config_show(), + ("set", Some(ssmatches)) => commands::config_set( + ssmatches.value_of("key").unwrap(), + ssmatches.value_of("value").unwrap(), + ), + _ => { + eprintln!("{}", smatches.usage()); + std::process::exit(1); + } + }, + ("login", Some(_)) => commands::login(), + ("unlock", Some(_)) => commands::unlock(), + ("sync", Some(_)) => commands::sync(), + ("list", Some(_)) => commands::list(), + ("get", Some(smatches)) => commands::get( + smatches.value_of("name").unwrap(), + smatches.value_of("user"), + ), + ("add", Some(_)) => commands::add(), + ("generate", Some(smatches)) => { + let ty = if smatches.is_present("no-symbols") { + rbw::pwgen::Type::NoSymbols + } else if smatches.is_present("only-numbers") { + rbw::pwgen::Type::Numbers + } else if smatches.is_present("nonconfusables") { + rbw::pwgen::Type::NonConfusables + } else if smatches.is_present("diceware") { + rbw::pwgen::Type::Diceware + } else { + rbw::pwgen::Type::AllChars + }; + commands::generate( + smatches.value_of("name"), + smatches.value_of("user"), + smatches.value_of("len").unwrap().parse().unwrap(), + ty, + ); + } + ("edit", Some(_)) => commands::edit(), + ("remove", Some(_)) => commands::remove(), + ("lock", Some(_)) => commands::lock(), + ("purge", Some(_)) => commands::purge(), + ("stop-agent", Some(_)) => commands::stop_agent(), + _ => { + eprintln!("{}", matches.usage()); + std::process::exit(1); + } + } +} diff --git a/src/bin/rbw/sock.rs b/src/bin/rbw/sock.rs new file mode 100644 index 0000000..05597e6 --- /dev/null +++ b/src/bin/rbw/sock.rs @@ -0,0 +1,29 @@ +use std::io::{BufRead as _, Write as _}; + +pub struct Sock(std::os::unix::net::UnixStream); + +impl Sock { + pub fn connect() -> Self { + Self( + std::os::unix::net::UnixStream::connect( + rbw::dirs::runtime_dir().join("socket"), + ) + .unwrap(), + ) + } + + pub fn send(&mut self, msg: &rbw::agent::Request) { + let Self(sock) = self; + sock.write_all(serde_json::to_string(msg).unwrap().as_bytes()) + .unwrap(); + sock.write_all(b"\n").unwrap(); + } + + pub fn recv(&mut self) -> rbw::agent::Response { + let Self(sock) = self; + let mut buf = std::io::BufReader::new(sock); + let mut line = String::new(); + buf.read_line(&mut line).unwrap(); + serde_json::from_str(&line).unwrap() + } +} -- cgit v1.2.3-54-g00ecf