feat(core): implement secure-input watcher

This commit is contained in:
Federico Terzi 2021-06-09 21:49:27 +02:00
parent e7df57ef87
commit 09095a1e8d
11 changed files with 212 additions and 4 deletions

14
Cargo.lock generated
View File

@ -427,6 +427,7 @@ dependencies = [
"espanso-info", "espanso-info",
"espanso-inject", "espanso-inject",
"espanso-ipc", "espanso-ipc",
"espanso-mac-utils",
"espanso-match", "espanso-match",
"espanso-migrate", "espanso-migrate",
"espanso-modulo", "espanso-modulo",
@ -548,6 +549,19 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "espanso-mac-utils"
version = "0.1.0"
dependencies = [
"anyhow",
"cc",
"lazy_static",
"lazycell",
"log",
"regex",
"thiserror",
]
[[package]] [[package]]
name = "espanso-match" name = "espanso-match"
version = "0.1.0" version = "0.1.0"

View File

@ -14,4 +14,5 @@ members = [
"espanso-path", "espanso-path",
"espanso-modulo", "espanso-modulo",
"espanso-migrate", "espanso-migrate",
"espanso-mac-utils",
] ]

View File

@ -58,3 +58,6 @@ notify = "4.0.17"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
named_pipe = "0.4.1" named_pipe = "0.4.1"
winapi = { version = "0.3.9", features = ["wincon"] } winapi = { version = "0.3.9", features = ["wincon"] }
[target.'cfg(target_os="macos")'.dependencies]
espanso-mac-utils = { path = "../espanso-mac-utils" }

View File

@ -29,6 +29,7 @@ use self::{modifier::{Modifier, ModifierStateStore}, sequencer::Sequencer};
pub mod detect; pub mod detect;
pub mod exit; pub mod exit;
pub mod modifier; pub mod modifier;
pub mod secure_input;
pub mod sequencer; pub mod sequencer;
pub mod ui; pub mod ui;

View File

@ -0,0 +1,66 @@
/*
* This file is part of espanso.
*
* Copyright (C) 2019-2021 Federico Terzi
*
* espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* espanso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use crossbeam::channel::{Receiver, Select, SelectedOperation};
use crate::{
cli::worker::secure_input::SecureInputEvent,
engine::{
event::{internal::SecureInputEnabledEvent, Event, EventType},
funnel,
},
};
use super::sequencer::Sequencer;
pub struct SecureInputSource<'a> {
pub receiver: Receiver<SecureInputEvent>,
pub sequencer: &'a Sequencer,
}
impl<'a> SecureInputSource<'a> {
pub fn new(receiver: Receiver<SecureInputEvent>, sequencer: &'a Sequencer) -> Self {
SecureInputSource {
receiver,
sequencer,
}
}
}
impl<'a> funnel::Source<'a> for SecureInputSource<'a> {
fn register(&'a self, select: &mut Select<'a>) -> usize {
select.recv(&self.receiver)
}
fn receive(&self, op: SelectedOperation) -> Event {
let si_event = op
.recv(&self.receiver)
.expect("unable to select data from SecureInputSource receiver");
Event {
source_id: self.sequencer.next_id(),
etype: match si_event {
SecureInputEvent::Disabled => EventType::SecureInputDisabled,
SecureInputEvent::Enabled { app_name, app_path } => {
EventType::SecureInputEnabled(SecureInputEnabledEvent { app_name, app_path })
}
},
}
}
}

View File

@ -54,6 +54,8 @@ use crate::{
engine::event::ExitMode, engine::event::ExitMode,
}; };
use super::secure_input::SecureInputEvent;
pub mod dispatch; pub mod dispatch;
pub mod funnel; pub mod funnel;
pub mod process; pub mod process;
@ -65,6 +67,7 @@ pub fn initialize_and_spawn(
ui_remote: Box<dyn UIRemote>, ui_remote: Box<dyn UIRemote>,
exit_signal: Receiver<()>, exit_signal: Receiver<()>,
ui_event_receiver: Receiver<UIEvent>, ui_event_receiver: Receiver<UIEvent>,
secure_input_receiver: Receiver<SecureInputEvent>,
) -> Result<JoinHandle<ExitMode>> { ) -> Result<JoinHandle<ExitMode>> {
let handle = std::thread::Builder::new() let handle = std::thread::Builder::new()
.name("engine thread".to_string()) .name("engine thread".to_string())
@ -86,8 +89,9 @@ pub fn initialize_and_spawn(
super::engine::funnel::init_and_spawn().expect("failed to initialize detector module"); super::engine::funnel::init_and_spawn().expect("failed to initialize detector module");
let exit_source = super::engine::funnel::exit::ExitSource::new(exit_signal, &sequencer); let exit_source = super::engine::funnel::exit::ExitSource::new(exit_signal, &sequencer);
let ui_source = super::engine::funnel::ui::UISource::new(ui_event_receiver, &sequencer); let ui_source = super::engine::funnel::ui::UISource::new(ui_event_receiver, &sequencer);
let secure_input_source = super::engine::funnel::secure_input::SecureInputSource::new(secure_input_receiver, &sequencer);
let sources: Vec<&dyn crate::engine::funnel::Source> = let sources: Vec<&dyn crate::engine::funnel::Source> =
vec![&detect_source, &exit_source, &ui_source]; vec![&detect_source, &exit_source, &ui_source, &secure_input_source];
let funnel = crate::engine::funnel::default(&sources); let funnel = crate::engine::funnel::default(&sources);
let rolling_matcher = RollingMatcherAdapter::new(&match_converter.get_rolling_matches()); let rolling_matcher = RollingMatcherAdapter::new(&match_converter.get_rolling_matches());

View File

@ -38,6 +38,7 @@ mod daemon_monitor;
mod engine; mod engine;
mod ipc; mod ipc;
mod match_cache; mod match_cache;
mod secure_input;
mod ui; mod ui;
pub fn new() -> CliModule { pub fn new() -> CliModule {
@ -100,6 +101,7 @@ fn worker_main(args: CliModuleArgs) -> i32 {
let (engine_exit_notify, engine_exit_receiver) = unbounded(); let (engine_exit_notify, engine_exit_receiver) = unbounded();
let (engine_ui_event_sender, engine_ui_event_receiver) = unbounded(); let (engine_ui_event_sender, engine_ui_event_receiver) = unbounded();
let (engine_secure_input_sender, engine_secure_input_receiver) = unbounded();
// Initialize the engine on another thread and start it // Initialize the engine on another thread and start it
let engine_handle = engine::initialize_and_spawn( let engine_handle = engine::initialize_and_spawn(
@ -109,6 +111,7 @@ fn worker_main(args: CliModuleArgs) -> i32 {
remote, remote,
engine_exit_receiver, engine_exit_receiver,
engine_ui_event_receiver, engine_ui_event_receiver,
engine_secure_input_receiver,
) )
.expect("unable to initialize engine"); .expect("unable to initialize engine");
@ -125,6 +128,9 @@ fn worker_main(args: CliModuleArgs) -> i32 {
.expect("unable to initialize daemon monitor thread"); .expect("unable to initialize daemon monitor thread");
} }
secure_input::initialize_and_spawn(engine_secure_input_sender)
.expect("unable to initialize secure input watcher");
eventloop eventloop
.run(Box::new(move |event| { .run(Box::new(move |event| {
if let Err(error) = engine_ui_event_sender.send(event) { if let Err(error) = engine_ui_event_sender.send(event) {

View File

@ -0,0 +1,107 @@
/*
* This file is part of espanso.
*
* Copyright (C) 2019-2021 Federico Terzi
*
* espanso is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* espanso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use std::time::Duration;
use anyhow::Result;
use crossbeam::channel::Sender;
use log::{error, info};
pub enum SecureInputEvent {
Disabled,
Enabled { app_name: String, app_path: String },
}
#[cfg(not(target_os = "macos"))]
pub fn initialize_and_spawn(_secure_input_send: Sender<SecureInputEvent>) -> Result<()> {
// NOOP on Windows and Linux
Ok(())
}
#[cfg(target_os = "macos")]
pub fn initialize_and_spawn(secure_input_sender: Sender<SecureInputEvent>) -> Result<()> {
std::thread::Builder::new()
.name("secure-input-monitor".to_string())
.spawn(move || {
// TODO: pass interval from config parameter
secure_input_main(secure_input_sender, std::time::Duration::from_secs(5));
})?;
Ok(())
}
#[cfg(target_os = "macos")]
fn secure_input_main(secure_input_sender: Sender<SecureInputEvent>, watch_interval: Duration) {
info!("monitoring the status of secure input");
let mut last_secure_input_pid: Option<i64> = None;
loop {
let pid = espanso_mac_utils::get_secure_input_pid();
if let Some(pid) = pid {
// Some application is currently on SecureInput
let should_notify = if let Some(old_pid) = last_secure_input_pid {
// We already detected a SecureInput app
if old_pid != pid {
// The old app is different from the current one, we should take action
true
} else {
// We already notified this application before
false
}
} else {
// First time we see this SecureInput app, we should take action
true
};
if should_notify {
let secure_input_app = espanso_mac_utils::get_secure_input_application();
if let Some((app_name, app_path)) = secure_input_app {
info!("secure input has been acquired by {}, preventing espanso from working correctly. Full path: {}", app_name, app_path);
if let Err(error) =
secure_input_sender.send(SecureInputEvent::Enabled { app_name, app_path })
{
error!("unable to send secure input disabled event: {}", error);
}
} else {
error!("detected secure input, but could not figure out which application triggered it");
}
}
last_secure_input_pid = Some(pid);
} else {
// No app is currently keeping SecureInput
// If there was an app with SecureInput, notify that is now free
if let Some(old_pid) = last_secure_input_pid {
info!("secure input has been disabled");
if let Err(error) = secure_input_sender.send(SecureInputEvent::Disabled) {
error!("unable to send secure input disabled event: {}", error);
}
}
last_secure_input_pid = None
}
std::thread::sleep(watch_interval);
}
}

View File

@ -83,3 +83,9 @@ pub struct DiscardPreviousEvent {
// All Events with a source_id smaller than this one will be discarded // All Events with a source_id smaller than this one will be discarded
pub minimum_source_id: u32, pub minimum_source_id: u32,
} }
#[derive(Debug, Clone, PartialEq)]
pub struct SecureInputEnabledEvent {
pub app_name: String,
pub app_path: String,
}

View File

@ -74,7 +74,7 @@ pub enum EventType {
Enabled, Enabled,
DisableRequest, DisableRequest,
EnableRequest, EnableRequest,
SecureInputEnabled, SecureInputEnabled(internal::SecureInputEnabledEvent),
SecureInputDisabled, SecureInputDisabled,
// Effects // Effects

View File

@ -51,7 +51,7 @@ impl Middleware for IconStatusMiddleware {
match &event.etype { match &event.etype {
EventType::Enabled => *enabled = true, EventType::Enabled => *enabled = true,
EventType::Disabled => *enabled = false, EventType::Disabled => *enabled = false,
EventType::SecureInputEnabled => *secure_input_enabled = true, EventType::SecureInputEnabled(_) => *secure_input_enabled = true,
EventType::SecureInputDisabled => *secure_input_enabled = false, EventType::SecureInputDisabled => *secure_input_enabled = false,
_ => did_update = false, _ => did_update = false,
} }