aboutsummaryrefslogtreecommitdiffstats
path: root/src/bin/rbw
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2020-04-11 18:53:45 -0400
committerJesse Luehrs <doy@tozt.net>2020-04-11 18:53:45 -0400
commitf65b65bb24960a75cf1f900819c4005e7729e834 (patch)
tree568998776208a3d9ba1c4adf738cef4a4fa28ea4 /src/bin/rbw
parent34053ffef22233c32b731acbf03d79f061e6c63b (diff)
downloadrbw-f65b65bb24960a75cf1f900819c4005e7729e834.tar.gz
rbw-f65b65bb24960a75cf1f900819c4005e7729e834.zip
refactor client code
Diffstat (limited to 'src/bin/rbw')
-rw-r--r--src/bin/rbw/actions.rs60
-rw-r--r--src/bin/rbw/commands.rs141
-rw-r--r--src/bin/rbw/main.rs107
-rw-r--r--src/bin/rbw/sock.rs29
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()
+ }
+}