1mod agent;
2mod args;
3mod auth;
4mod cmd;
5mod environment;
6mod expansions;
7pub mod filter;
8mod logging;
9#[cfg(feature = "native-crypto")]
10mod nativecrypto;
11mod pamext;
12#[cfg(test)]
13mod test;
14mod verify;
15
16pub use crate::agent::SSHAgent;
17pub use crate::auth::authenticate;
18use pam::constants::{PamFlag, PamResultCode};
19use pam::module::{PamHandle, PamHooks};
20use std::env;
21use std::env::VarError;
22
23use crate::environment::UnixEnvironment;
24use crate::filter::IdentityFilter;
25use crate::logging::init_logging;
26use crate::pamext::PamHandleExt;
27use anyhow::{anyhow, Result};
28use args::Args;
29use log::{error, info};
30use ssh_agent_client_rs::Client;
31use std::ffi::CStr;
32use std::path::Path;
33
34struct PamSshAgent;
35pam::pam_hooks!(PamSshAgent);
36
37impl PamHooks for PamSshAgent {
38 fn sm_authenticate(
46 pam_handle: &mut PamHandle,
47 args: Vec<&CStr>,
48 _flags: PamFlag,
49 ) -> PamResultCode {
50 match run(args, pam_handle) {
51 Ok(_) => PamResultCode::PAM_SUCCESS,
52 Err(err) => {
53 for line in format!("{err:?}").split('\n') {
54 error!("{line}")
55 }
56 PamResultCode::PAM_AUTH_ERR
57 }
58 }
59 }
60
61 fn sm_setcred(
64 _pam_handle: &mut PamHandle,
65 _args: Vec<&CStr>,
66 _flags: PamFlag,
67 ) -> PamResultCode {
68 PamResultCode::PAM_SUCCESS
69 }
70}
71
72fn run(args: Vec<&CStr>, pam_handle: &PamHandle) -> Result<()> {
73 init_logging(pam_handle.get_service().unwrap_or("unknown".into()).into())?;
74 let args = Args::parse(args, &UnixEnvironment, pam_handle)?;
75 if args.debug {
76 log::set_max_level(log::LevelFilter::Debug);
77 }
78 do_authenticate(&args, pam_handle)?;
79 Ok(())
80}
81
82fn do_authenticate(args: &Args, handle: &PamHandle) -> Result<()> {
83 let path = get_path(args)?;
84
85 info!("Authenticating using ssh-agent at '{path}'");
86 if Path::new(&args.file).exists() {
87 info!("authorized keys from '{}'", &args.file);
88 }
89 if let Some(ca_keys_file) = &args.ca_keys_file {
90 info!("ca_keys from '{ca_keys_file}'");
91 };
92 if let Some(authorized_keys_command) = &args.authorized_keys_command {
93 info!("Invoking command '{authorized_keys_command}' to obtain keys");
94 }
95
96 let ssh_agent_client = Client::connect(Path::new(path.as_str()))?;
97
98 let filter = IdentityFilter::new(
99 Path::new(args.file.as_str()),
100 args.ca_keys_file.as_deref().map(Path::new),
101 args.authorized_keys_command.as_deref(),
102 args.authorized_keys_command_user.as_deref(),
103 &handle.get_calling_user()?,
104 )?;
105 match authenticate(&filter, ssh_agent_client, &handle.get_calling_user()?)? {
106 true => Ok(()),
107 false => Err(anyhow!("Agent did not know of any of the allowed keys")),
108 }
109}
110
111fn get_path(args: &Args) -> Result<String> {
112 match env::var("SSH_AUTH_SOCK") {
113 Ok(path) => return Ok(path),
114 Err(VarError::NotPresent) => {}
116 Err(_) => {
117 return Err(anyhow!("Failed to read environment variable SSH_AUTH_SOCK"));
118 }
119 }
120 match &args.default_ssh_auth_sock {
121 Some(path) => Ok(path.to_string()),
122 None => Err(anyhow!(
123 "SSH_AUTH_SOCK not set and the default_ssh_auth_sock parameter is not set"
124 )),
125 }
126}