mod agent;
mod args;
mod auth;
mod expansions;
mod log;
#[cfg(feature = "native-crypto")]
mod nativecrypto;
mod verify;
pub use crate::agent::SSHAgent;
pub use crate::auth::authenticate;
pub use crate::log::PrintLog;
use pam::constants::{PamFlag, PamResultCode};
use pam::module::{PamHandle, PamHooks};
use std::env;
use std::env::VarError;
use crate::expansions::UnixEnvironment;
use crate::log::{Log, SyslogLogger};
use anyhow::{anyhow, Result};
use args::Args;
use pam::items::Service;
use ssh_agent_client_rs::Client;
use std::ffi::CStr;
use std::path::Path;
struct PamSshAgent;
pam::pam_hooks!(PamSshAgent);
impl PamHooks for PamSshAgent {
fn sm_authenticate(
pam_handle: &mut PamHandle,
args: Vec<&CStr>,
_flags: PamFlag,
) -> PamResultCode {
let mut log = SyslogLogger::new(&get_service(pam_handle), Args::default().debug);
let args = match Args::parse(args, Some(&UnixEnvironment::new(pam_handle))) {
Ok(args) => args,
Err(e) => {
log.error(format!("Failed to parse args: {e}"))
.expect("Failed to log");
return PamResultCode::PAM_SERVICE_ERR;
}
};
if args.debug != Args::default().debug {
log = SyslogLogger::new(&get_service(pam_handle), args.debug);
}
match do_authenticate(&mut log, &args) {
Ok(_) => PamResultCode::PAM_SUCCESS,
Err(err) => {
for line in format!("{err:?}").split('\n') {
log.error(line).expect("Failed to log");
}
PamResultCode::PAM_AUTH_ERR
}
}
}
fn sm_setcred(
_pam_handle: &mut PamHandle,
_args: Vec<&CStr>,
_flags: PamFlag,
) -> PamResultCode {
PamResultCode::PAM_SUCCESS
}
}
fn do_authenticate(log: &mut impl Log, args: &Args) -> Result<()> {
let path = get_path(args)?;
log.info(format!(
"Authenticating using ssh-agent at '{}' and keys from '{}'",
path, args.file
))?;
let ssh_agent_client = Client::connect(Path::new(path.as_str()))?;
match authenticate(args.file.as_str(), ssh_agent_client, log)? {
true => Ok(()),
false => Err(anyhow!("Agent did not know of any of the allowed keys")),
}
}
fn get_path(args: &Args) -> Result<String> {
match env::var("SSH_AUTH_SOCK") {
Ok(path) => return Ok(path),
Err(VarError::NotPresent) => {}
Err(_) => {
return Err(anyhow!("Failed to read environment variable SSH_AUTH_SOCK"));
}
}
match &args.default_ssh_auth_sock {
Some(path) => Ok(path.to_string()),
None => Err(anyhow!(
"SSH_AUTH_SOCK not set and the default_ssh_auth_sock parameter is not set"
)),
}
}
fn get_service(pam_handle: &PamHandle) -> String {
let service = match pam_handle.get_item::<Service>() {
Ok(Some(service)) => service,
_ => return "unknown".into(),
};
String::from_utf8_lossy(service.0.to_bytes()).to_string()
}