diff options
author | Jesse Luehrs <doy@tozt.net> | 2020-04-11 18:53:45 -0400 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2020-04-11 18:53:45 -0400 |
commit | f65b65bb24960a75cf1f900819c4005e7729e834 (patch) | |
tree | 568998776208a3d9ba1c4adf738cef4a4fa28ea4 /src/bin/rbw | |
parent | 34053ffef22233c32b731acbf03d79f061e6c63b (diff) | |
download | rbw-f65b65bb24960a75cf1f900819c4005e7729e834.tar.gz rbw-f65b65bb24960a75cf1f900819c4005e7729e834.zip |
refactor client code
Diffstat (limited to 'src/bin/rbw')
-rw-r--r-- | src/bin/rbw/actions.rs | 60 | ||||
-rw-r--r-- | src/bin/rbw/commands.rs | 141 | ||||
-rw-r--r-- | src/bin/rbw/main.rs | 107 | ||||
-rw-r--r-- | src/bin/rbw/sock.rs | 29 |
4 files changed, 337 insertions, 0 deletions
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() + } +} |