aboutsummaryrefslogtreecommitdiffstats
path: root/src/bin/rbw-agent.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/rbw-agent.rs')
-rw-r--r--src/bin/rbw-agent.rs239
1 files changed, 239 insertions, 0 deletions
diff --git a/src/bin/rbw-agent.rs b/src/bin/rbw-agent.rs
new file mode 100644
index 0000000..9d4f7c5
--- /dev/null
+++ b/src/bin/rbw-agent.rs
@@ -0,0 +1,239 @@
+use tokio::io::{AsyncBufReadExt as _, AsyncWriteExt as _};
+use tokio::stream::StreamExt as _;
+
+fn make_socket() -> anyhow::Result<tokio::net::UnixListener> {
+ let runtime_dir = rbw::dirs::runtime_dir();
+ std::fs::create_dir_all(&runtime_dir)?;
+
+ let path = runtime_dir.join("socket");
+ std::fs::remove_file(&path)?;
+ let sock = tokio::net::UnixListener::bind(&path)?;
+ log::debug!("listening on socket {}", path.to_string_lossy());
+ Ok(sock)
+}
+
+async fn send_response(
+ sock: &mut tokio::net::UnixStream,
+ res: &rbw::agent::Response,
+) {
+ sock.write_all(serde_json::to_string(res).unwrap().as_bytes())
+ .await
+ .unwrap();
+ sock.write_all(b"\n").await.unwrap();
+}
+
+async fn login(
+ sock: &mut tokio::net::UnixStream,
+ state: std::sync::Arc<tokio::sync::RwLock<State>>,
+ tty: Option<&str>,
+) {
+ let mut state = state.write().await;
+
+ let email = config_email().await;
+ let password =
+ rbw::pinentry::getpin("prompt", "desc", tty).await.unwrap();
+
+ let (access_token, refresh_token, iterations, protected_key, keys) =
+ rbw::actions::login(&email, &password).await.unwrap();
+
+ state.priv_key = Some(keys);
+
+ let mut db = rbw::db::Db::load_async(&email)
+ .await
+ .unwrap_or_else(|_| rbw::db::Db::new());
+ db.access_token = Some(access_token);
+ db.refresh_token = Some(refresh_token);
+ db.iterations = Some(iterations);
+ db.protected_key = Some(protected_key);
+ db.save_async(&email).await.unwrap();
+
+ send_response(sock, &rbw::agent::Response::Ack).await;
+}
+
+async fn unlock(
+ sock: &mut tokio::net::UnixStream,
+ state: std::sync::Arc<tokio::sync::RwLock<State>>,
+ tty: Option<&str>,
+) {
+ let mut state = state.write().await;
+
+ let email = config_email().await;
+ let password =
+ rbw::pinentry::getpin("prompt", "desc", tty).await.unwrap();
+
+ let db = rbw::db::Db::load_async(&email)
+ .await
+ .unwrap_or_else(|_| rbw::db::Db::new());
+
+ let keys = rbw::actions::unlock(
+ &email,
+ &password,
+ db.iterations.unwrap(),
+ db.protected_key.as_deref().unwrap(),
+ )
+ .await
+ .unwrap();
+
+ state.priv_key = Some(keys);
+
+ send_response(sock, &rbw::agent::Response::Ack).await;
+}
+
+async fn lock(
+ sock: &mut tokio::net::UnixStream,
+ state: std::sync::Arc<tokio::sync::RwLock<State>>,
+) {
+ let mut state = state.write().await;
+
+ state.priv_key = None;
+
+ send_response(sock, &rbw::agent::Response::Ack).await;
+}
+
+async fn sync(sock: &mut tokio::net::UnixStream) {
+ let email = config_email().await;
+ let mut db = rbw::db::Db::load_async(&email)
+ .await
+ .unwrap_or_else(|_| rbw::db::Db::new());
+
+ let (protected_key, ciphers) =
+ rbw::actions::sync(db.access_token.as_ref().unwrap())
+ .await
+ .unwrap();
+ db.protected_key = Some(protected_key);
+ db.ciphers = ciphers;
+ db.save_async(&email).await.unwrap();
+
+ send_response(sock, &rbw::agent::Response::Ack).await;
+}
+
+async fn decrypt(
+ sock: &mut tokio::net::UnixStream,
+ state: std::sync::Arc<tokio::sync::RwLock<State>>,
+ cipherstring: &str,
+) {
+ let state = state.read().await;
+ let keys = state.priv_key.as_ref().unwrap();
+ let cipherstring =
+ rbw::cipherstring::CipherString::new(cipherstring).unwrap();
+ let plaintext =
+ String::from_utf8(cipherstring.decrypt(keys).unwrap()).unwrap();
+
+ send_response(sock, &rbw::agent::Response::Decrypt { plaintext }).await;
+}
+
+async fn handle_sock(
+ sock: tokio::net::UnixStream,
+ state: std::sync::Arc<tokio::sync::RwLock<State>>,
+) {
+ let mut buf = tokio::io::BufStream::new(sock);
+ let mut line = String::new();
+ buf.read_line(&mut line).await.unwrap();
+ let mut sock = buf.into_inner();
+ let msg: rbw::agent::Request = serde_json::from_str(&line).unwrap();
+ match msg.action {
+ rbw::agent::Action::Login => {
+ login(&mut sock, state.clone(), msg.tty.as_deref()).await
+ }
+ rbw::agent::Action::Unlock => {
+ unlock(&mut sock, state.clone(), msg.tty.as_deref()).await
+ }
+ rbw::agent::Action::Lock => lock(&mut sock, state.clone()).await,
+ rbw::agent::Action::Sync => sync(&mut sock).await,
+ rbw::agent::Action::Decrypt { cipherstring } => {
+ decrypt(&mut sock, state.clone(), &cipherstring).await
+ }
+ rbw::agent::Action::Quit => std::process::exit(0),
+ }
+}
+
+async fn config_email() -> String {
+ let config = rbw::config::Config::load_async().await.unwrap();
+ config.email.unwrap()
+}
+
+struct Agent {
+ timeout: tokio::time::Delay,
+ state: std::sync::Arc<tokio::sync::RwLock<State>>,
+}
+
+struct State {
+ priv_key: Option<rbw::locked::Keys>,
+}
+
+impl Agent {
+ fn new() -> Self {
+ let config = rbw::config::Config::load().unwrap();
+ Self {
+ timeout: tokio::time::delay_for(
+ tokio::time::Duration::from_secs(config.lock_timeout),
+ ),
+ state: std::sync::Arc::new(tokio::sync::RwLock::new(State {
+ priv_key: None,
+ })),
+ }
+ }
+
+ async fn run(&mut self, mut listener: tokio::net::UnixListener) {
+ loop {
+ tokio::select! {
+ sock = listener.next() => {
+ let state = self.state.clone();
+ tokio::spawn(async move {
+ handle_sock(sock.unwrap().unwrap(), state).await
+ });
+ }
+ _ = &mut self.timeout => {
+ self.state.write().await.priv_key = None
+ }
+ }
+ }
+ }
+}
+
+fn main() {
+ env_logger::from_env(
+ env_logger::Env::default().default_filter_or("info"),
+ )
+ .init();
+
+ let runtime_dir = rbw::dirs::runtime_dir();
+ std::fs::create_dir_all(&runtime_dir).unwrap();
+
+ let (r, w) = nix::unistd::pipe().unwrap();
+ let res = daemonize::Daemonize::new()
+ .pid_file(runtime_dir.join("pidfile"))
+ .exit_action(move || {
+ nix::unistd::close(w).unwrap();
+ let mut buf = [0; 1];
+ nix::unistd::read(r, &mut buf).unwrap();
+ nix::unistd::close(r).unwrap();
+ })
+ .start();
+ nix::unistd::close(r).unwrap();
+
+ match res {
+ Ok(_) => (),
+ Err(e) => {
+ match e {
+ daemonize::DaemonizeError::LockPidfile(_) => {
+ // this means that there is already an agent running, so
+ // return a special exit code to allow the cli to detect
+ // this case and not error out
+ std::process::exit(23);
+ }
+ _ => panic!("failed to daemonize: {}", e),
+ }
+ }
+ }
+
+ tokio::runtime::Runtime::new().unwrap().block_on(async {
+ let listener = make_socket();
+
+ nix::unistd::write(w, &[0]).unwrap();
+ nix::unistd::close(w).unwrap();
+
+ let mut agent = Agent::new();
+ agent.run(listener.unwrap()).await;
+ })
+}