1mod agent;
2mod args;
3mod auth;
4mod log;
5
6pub use crate::agent::SSHAgent;
7pub use crate::auth::authenticate;
8pub use crate::log::PrintLog;
9use std::env;
10
11use pam::constants::{PamFlag, PamResultCode};
12use pam::module::{PamHandle, PamHooks};
13
14use crate::log::{Log, SyslogLogger};
15use anyhow::{anyhow, Context, Result};
16use args::Args;
17use pam::items::Service;
18use ssh_agent_client_rs::Client;
19use std::ffi::CStr;
20use std::path::Path;
21
22struct PamSshAgent;
23pam::pam_hooks!(PamSshAgent);
24
25impl PamHooks for PamSshAgent {
26 fn sm_authenticate(
34 pam_handle: &mut PamHandle,
35 args: Vec<&CStr>,
36 _flags: PamFlag,
37 ) -> PamResultCode {
38 let args = Args::parse(args);
39 let mut log = SyslogLogger::new(&get_service(pam_handle), args.debug);
40
41 match do_authenticate(&mut log, &args) {
42 Ok(_) => PamResultCode::PAM_SUCCESS,
43 Err(err) => {
44 for line in format!("{err:?}").split('\n') {
45 log.error(line).expect("Failed to log");
46 }
47 PamResultCode::PAM_AUTH_ERR
48 }
49 }
50 }
51
52 fn sm_setcred(
55 _pam_handle: &mut PamHandle,
56 _args: Vec<&CStr>,
57 _flags: PamFlag,
58 ) -> PamResultCode {
59 PamResultCode::PAM_SUCCESS
60 }
61}
62
63fn do_authenticate(log: &mut impl Log, args: &Args) -> Result<()> {
64 let path = env::var("SSH_AUTH_SOCK")
65 .context("Required environment variable SSH_AUTH_SOCK is not set")?;
66 log.info(format!(
67 "Authenticating using ssh-agent at '{}' and keys from '{}'",
68 path, args.file
69 ))?;
70 let ssh_agent_client = Client::connect(Path::new(path.as_str()))?;
71 match authenticate(args.file.as_str(), ssh_agent_client, log)? {
72 true => Ok(()),
73 false => Err(anyhow!("Agent did not know of any of the allowed keys")),
74 }
75}
76
77fn get_service(pam_handle: &PamHandle) -> String {
80 let service = match pam_handle.get_item::<Service>() {
81 Ok(Some(service)) => service,
82 _ => return "unknown".into(),
83 };
84 String::from_utf8_lossy(service.0.to_bytes()).to_string()
85}