aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2023-07-18 02:52:22 -0400
committerJesse Luehrs <doy@tozt.net>2023-07-18 03:24:33 -0400
commitcf75da99817cadba82299ef3d106da5308433486 (patch)
tree9b7f74b1d6da0c1472a8e6cd00857d547e59b184
parent95c41b75e12d2cee4758c80cf36ab5e3331565e5 (diff)
downloadrbw-cf75da99817cadba82299ef3d106da5308433486.tar.gz
rbw-cf75da99817cadba82299ef3d106da5308433486.zip
make clipboard manipulation happen from the agent
on x11 systems, you can't just send data to the os to store on the clipboard, you just register which application currently owns the clipboard and then other applications can use ipc to request the owning application to send them the clipboard data. this requires there to be an application still running in order to respond to those requests. luckily, we have one of those available in the form of the agent.
-rw-r--r--src/bin/rbw-agent/actions.rs54
-rw-r--r--src/bin/rbw-agent/agent.rs26
-rw-r--r--src/bin/rbw/actions.rs6
-rw-r--r--src/bin/rbw/commands.rs10
-rw-r--r--src/protocol.rs3
5 files changed, 66 insertions, 33 deletions
diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs
index 1974736..c44f601 100644
--- a/src/bin/rbw-agent/actions.rs
+++ b/src/bin/rbw-agent/actions.rs
@@ -1,4 +1,5 @@
use anyhow::Context as _;
+use copypasta::ClipboardProvider as _;
pub async fn register(
sock: &mut crate::sock::Sock,
@@ -79,7 +80,7 @@ pub async fn register(
pub async fn login(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
tty: Option<&str>,
) -> anyhow::Result<()> {
let db = load_db().await.unwrap_or_else(|_| rbw::db::Db::new());
@@ -311,7 +312,7 @@ async fn two_factor(
}
async fn login_success(
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
access_token: String,
refresh_token: String,
kdf: rbw::api::KdfType,
@@ -356,7 +357,7 @@ async fn login_success(
match res {
Ok((keys, org_keys)) => {
- let mut state = state.write().await;
+ let mut state = state.lock().await;
state.priv_key = Some(keys);
state.org_keys = Some(org_keys);
}
@@ -368,10 +369,10 @@ async fn login_success(
pub async fn unlock(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
tty: Option<&str>,
) -> anyhow::Result<()> {
- if state.read().await.needs_unlock() {
+ if state.lock().await.needs_unlock() {
let db = load_db().await?;
let Some(kdf) = db.kdf
@@ -464,11 +465,11 @@ pub async fn unlock(
}
async fn unlock_success(
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
keys: rbw::locked::Keys,
org_keys: std::collections::HashMap<String, rbw::locked::Keys>,
) -> anyhow::Result<()> {
- let mut state = state.write().await;
+ let mut state = state.lock().await;
state.priv_key = Some(keys);
state.org_keys = Some(org_keys);
Ok(())
@@ -476,9 +477,9 @@ async fn unlock_success(
pub async fn lock(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
) -> anyhow::Result<()> {
- state.write().await.clear();
+ state.lock().await.clear();
respond_ack(sock).await?;
@@ -487,10 +488,10 @@ pub async fn lock(
pub async fn check_lock(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
_tty: Option<&str>,
) -> anyhow::Result<()> {
- if state.read().await.needs_unlock() {
+ if state.lock().await.needs_unlock() {
return Err(anyhow::anyhow!("agent is locked"));
}
@@ -538,11 +539,11 @@ pub async fn sync(
pub async fn decrypt(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
cipherstring: &str,
org_id: Option<&str>,
) -> anyhow::Result<()> {
- let state = state.read().await;
+ let state = state.lock().await;
let Some(keys) = state.key(org_id)
else {
return Err(anyhow::anyhow!(
@@ -565,11 +566,11 @@ pub async fn decrypt(
pub async fn encrypt(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
plaintext: &str,
org_id: Option<&str>,
) -> anyhow::Result<()> {
- let state = state.read().await;
+ let state = state.lock().await;
let Some(keys) = state.key(org_id)
else {
return Err(anyhow::anyhow!(
@@ -587,6 +588,25 @@ pub async fn encrypt(
Ok(())
}
+pub async fn clipboard_store(
+ sock: &mut crate::sock::Sock,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
+ text: &str,
+) -> anyhow::Result<()> {
+ state
+ .lock()
+ .await
+ .clipboard
+ .set_contents(text.to_owned())
+ .map_err(|e| {
+ anyhow::anyhow!("couldn't store value to clipboard: {e}")
+ })?;
+
+ respond_ack(sock).await?;
+
+ Ok(())
+}
+
pub async fn version(sock: &mut crate::sock::Sock) -> anyhow::Result<()> {
sock.send(&rbw::protocol::Response::Version {
version: rbw::protocol::version(),
@@ -663,7 +683,7 @@ async fn config_pinentry() -> anyhow::Result<String> {
}
pub async fn subscribe_to_notifications(
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
) -> anyhow::Result<()> {
// access token might be out of date, so we do a sync to refresh it
sync(None).await?;
@@ -685,7 +705,7 @@ pub async fn subscribe_to_notifications(
+ "/notifications/hub?access_token=";
websocket_url.push_str(&access_token);
- let mut state = state.write().await;
+ let mut state = state.lock().await;
let err = state
.notifications_handler
.connect(websocket_url)
diff --git a/src/bin/rbw-agent/agent.rs b/src/bin/rbw-agent/agent.rs
index d4b3341..fe46e3b 100644
--- a/src/bin/rbw-agent/agent.rs
+++ b/src/bin/rbw-agent/agent.rs
@@ -12,6 +12,7 @@ pub struct State {
pub sync_timeout: crate::timeout::Timeout,
pub sync_timeout_duration: std::time::Duration,
pub notifications_handler: crate::notifications::Handler,
+ pub clipboard: copypasta::ClipboardContext,
}
impl State {
@@ -43,7 +44,7 @@ impl State {
pub struct Agent {
timer_r: tokio::sync::mpsc::UnboundedReceiver<()>,
sync_timer_r: tokio::sync::mpsc::UnboundedReceiver<()>,
- state: std::sync::Arc<tokio::sync::RwLock<State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<State>>,
}
impl Agent {
@@ -59,10 +60,13 @@ impl Agent {
sync_timeout.set(sync_timeout_duration);
}
let notifications_handler = crate::notifications::Handler::new();
+ let clipboard = copypasta::ClipboardContext::new().map_err(|e| {
+ anyhow::anyhow!("couldn't create clipboard context: {e}")
+ })?;
Ok(Self {
timer_r,
sync_timer_r,
- state: std::sync::Arc::new(tokio::sync::RwLock::new(State {
+ state: std::sync::Arc::new(tokio::sync::Mutex::new(State {
priv_key: None,
org_keys: None,
timeout,
@@ -70,6 +74,7 @@ impl Agent {
sync_timeout,
sync_timeout_duration,
notifications_handler,
+ clipboard,
})),
})
}
@@ -95,7 +100,7 @@ impl Agent {
notifications::NotificationMessage,
> = {
self.state
- .write()
+ .lock()
.await
.notifications_handler
.get_channel()
@@ -148,7 +153,7 @@ impl Agent {
});
}
Event::Timeout(()) => {
- self.state.write().await.clear();
+ self.state.lock().await.clear();
}
Event::Sync(()) => {
// this could fail if we aren't logged in, but we don't
@@ -159,7 +164,7 @@ impl Agent {
if let Err(e) = result {
eprintln!("failed to sync: {e:#}");
} else if !state
- .write()
+ .lock()
.await
.notifications_handler
.is_connected()
@@ -174,7 +179,7 @@ impl Agent {
}
}
});
- self.state.write().await.set_sync_timeout();
+ self.state.lock().await.set_sync_timeout();
}
}
}
@@ -184,7 +189,7 @@ impl Agent {
async fn handle_request(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<State>>,
) -> anyhow::Result<()> {
let req = sock.recv().await?;
let req = match req {
@@ -249,6 +254,11 @@ async fn handle_request(
.await?;
true
}
+ rbw::protocol::Action::ClipboardStore { text } => {
+ crate::actions::clipboard_store(sock, state.clone(), text)
+ .await?;
+ true
+ }
rbw::protocol::Action::Quit => std::process::exit(0),
rbw::protocol::Action::Version => {
crate::actions::version(sock).await?;
@@ -257,7 +267,7 @@ async fn handle_request(
};
if set_timeout {
- state.write().await.set_timeout();
+ state.lock().await.set_timeout();
}
Ok(())
diff --git a/src/bin/rbw/actions.rs b/src/bin/rbw/actions.rs
index 29cd6fe..e0e7a2e 100644
--- a/src/bin/rbw/actions.rs
+++ b/src/bin/rbw/actions.rs
@@ -101,6 +101,12 @@ pub fn encrypt(
}
}
+pub fn clipboard_store(text: &str) -> anyhow::Result<()> {
+ simple_action(rbw::protocol::Action::ClipboardStore {
+ text: text.to_string(),
+ })
+}
+
pub fn version() -> anyhow::Result<u32> {
let mut sock = connect()?;
sock.send(&rbw::protocol::Request {
diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs
index 03f07b3..10f981d 100644
--- a/src/bin/rbw/commands.rs
+++ b/src/bin/rbw/commands.rs
@@ -1,5 +1,4 @@
use anyhow::Context as _;
-use copypasta::{ClipboardContext, ClipboardProvider};
use serde::Serialize;
use std::io;
use std::io::prelude::Write;
@@ -741,13 +740,8 @@ pub fn config_unset(key: &str) -> anyhow::Result<()> {
}
fn clipboard_store(val: &str) -> anyhow::Result<()> {
- let mut ctx = ClipboardContext::new().map_err(|e| {
- anyhow::anyhow!("couldn't create clipboard context: {e}")
- })?;
-
- ctx.set_contents(val.to_owned()).map_err(|e| {
- anyhow::anyhow!("couldn't store value to clipboard: {e}")
- })?;
+ ensure_agent()?;
+ crate::actions::clipboard_store(val)?;
Ok(())
}
diff --git a/src/protocol.rs b/src/protocol.rs
index f9a9d28..e883441 100644
--- a/src/protocol.rs
+++ b/src/protocol.rs
@@ -34,6 +34,9 @@ pub enum Action {
plaintext: String,
org_id: Option<String>,
},
+ ClipboardStore {
+ text: String,
+ },
Quit,
Version,
}