pub mod config;
pub mod daemon;
pub mod error;
pub mod protocol;
pub mod session;
use std::path::PathBuf;
use std::time::Duration;
pub use config::{AGENT_CONFIG_FILE, config_path, load_scope};
pub use daemon::{SessionOwned, default_socket_path};
pub use error::AgentError;
pub use session::{KeypairEntry, Session};
use kovra_core::{AgentScope, AuditSink, Clock, Confirmer};
pub struct AgentConfig {
pub socket_path: PathBuf,
pub scope: AgentScope,
pub confirm_timeout: Duration,
pub requesting_process: Option<String>,
}
pub trait SessionProvider {
fn load_keys(&self) -> Result<Vec<KeypairEntry>, AgentError>;
fn confirmer(&self) -> Box<dyn Confirmer>;
fn audit(&self) -> Box<dyn AuditSink>;
fn clock(&self) -> Box<dyn Clock>;
}
pub fn run_agent<P: SessionProvider>(config: AgentConfig, provider: P) -> Result<(), AgentError> {
daemon::ensure_no_existing_agent()?;
let listener = daemon::bind(&config.socket_path)?;
let path_display = config.socket_path.display().to_string();
install_signal_cleanup(&config.socket_path);
eprintln!(
"kovra ssh-agent listening on {path_display}\n\
Export it in the shells that should use kovra as their agent:\n\
\n export SSH_AUTH_SOCK={path_display}\n\
\nServing in the foreground — press Ctrl-C to stop. \
(Governs the auth event, not the SSH session that follows — spec §16.)"
);
let confirm_timeout = config.confirm_timeout;
let scope = config.scope;
let requesting_process = config.requesting_process;
let result = daemon::serve(&listener, || {
Ok(SessionOwned {
keys: provider.load_keys()?,
scope: scope.clone(),
confirmer: provider.confirmer(),
audit: provider.audit(),
clock: provider.clock(),
confirm_timeout,
requesting_process: requesting_process.clone(),
})
});
daemon::cleanup(&config.socket_path);
result
}
#[cfg(unix)]
fn install_signal_cleanup(path: &std::path::Path) {
use std::sync::OnceLock;
static SOCK: OnceLock<PathBuf> = OnceLock::new();
let _ = SOCK.set(path.to_path_buf());
extern "C" fn handler(_sig: i32) {
if let Some(p) = SOCK.get() {
if let Ok(c) = std::ffi::CString::new(p.as_os_str().to_string_lossy().as_bytes()) {
unsafe {
libc::unlink(c.as_ptr());
}
}
}
std::process::exit(130);
}
let handler_ptr = handler as *const () as libc::sighandler_t;
unsafe {
libc::signal(libc::SIGINT, handler_ptr);
libc::signal(libc::SIGTERM, handler_ptr);
}
}
#[cfg(not(unix))]
fn install_signal_cleanup(_path: &std::path::Path) {}