use std::io::Write;
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::{Path, PathBuf};
use crate::error::AgentError;
use crate::protocol::{encode_failure, frame, parse_request, read_frame};
use crate::session::Session;
pub fn ensure_no_existing_agent() -> Result<(), AgentError> {
if let Some(sock) = std::env::var_os("SSH_AUTH_SOCK") {
return Err(AgentError::AuthSockAlreadySet(
sock.to_string_lossy().into_owned(),
));
}
Ok(())
}
pub fn bind(path: &Path) -> Result<UnixListener, AgentError> {
if path.exists() {
let is_socket = std::fs::symlink_metadata(path)
.map(|m| {
use std::os::unix::fs::FileTypeExt;
m.file_type().is_socket()
})
.unwrap_or(false);
if is_socket {
let _ = std::fs::remove_file(path);
} else {
return Err(AgentError::Socket(format!(
"{} exists and is not a socket — refusing to overwrite",
path.display()
)));
}
}
let listener = UnixListener::bind(path)
.map_err(|e| AgentError::Socket(format!("bind {}: {e}", path.display())))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
.map_err(|e| AgentError::Socket(format!("chmod {}: {e}", path.display())))?;
}
Ok(listener)
}
pub fn default_socket_path(root: &Path) -> PathBuf {
root.join("agent.sock")
}
pub fn serve<F>(listener: &UnixListener, mut make_session: F) -> Result<(), AgentError>
where
F: FnMut() -> Result<SessionOwned, AgentError>,
{
for incoming in listener.incoming() {
match incoming {
Ok(stream) => {
if let Err(e) = handle_connection(stream, &mut make_session) {
eprintln!("kovra ssh-agent: connection error: {e}");
}
}
Err(e) => {
eprintln!("kovra ssh-agent: accept error: {e}");
}
}
}
Ok(())
}
pub struct SessionOwned {
pub keys: Vec<crate::session::KeypairEntry>,
pub scope: kovra_core::AgentScope,
pub confirmer: Box<dyn kovra_core::Confirmer>,
pub audit: Box<dyn kovra_core::AuditSink>,
pub clock: Box<dyn kovra_core::Clock>,
pub confirm_timeout: std::time::Duration,
pub requesting_process: Option<String>,
}
impl SessionOwned {
fn as_session(&self) -> Session<'_> {
Session {
keys: &self.keys,
scope: &self.scope,
confirmer: self.confirmer.as_ref(),
audit: self.audit.as_ref(),
clock: self.clock.as_ref(),
confirm_timeout: self.confirm_timeout,
requesting_process: self.requesting_process.clone(),
}
}
}
fn handle_connection<F>(mut stream: UnixStream, make_session: &mut F) -> Result<(), AgentError>
where
F: FnMut() -> Result<SessionOwned, AgentError>,
{
loop {
let body = match read_frame(&mut stream)? {
Some(b) => b,
None => return Ok(()), };
let reply_body = match parse_request(&body) {
Ok(request) => {
let owned = make_session()?;
let session = owned.as_session();
session.handle(&request)?
}
Err(_) => encode_failure(),
};
stream.write_all(&frame(&reply_body))?;
stream.flush()?;
}
}
pub fn cleanup(path: &Path) {
let _ = std::fs::remove_file(path);
}