aboutsummaryrefslogtreecommitdiffstats
path: root/src
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
parent34053ffef22233c32b731acbf03d79f061e6c63b (diff)
downloadrbw-f65b65bb24960a75cf1f900819c4005e7729e834.tar.gz
rbw-f65b65bb24960a75cf1f900819c4005e7729e834.zip
refactor client code
Diffstat (limited to 'src')
-rw-r--r--src/bin/rbw.rs328
-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
5 files changed, 337 insertions, 328 deletions
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()
+ }
+}