pam_ssh_agent/
lib.rs

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    /// The authentication method called by pam to authenticate the user. This method
39    /// will return PAM_SUCCESS if the ssh-agent available through the unix socket path
40    /// in the PAM_AUTH_SOCK environment variable is able to correctly sign a random
41    /// message with the private key corresponding to one of the public keys in
42    /// /etc/security/authorized_key. Otherwise, this function returns PAM_AUTH_ERR.
43    ///
44    /// This method logs diagnostic output to the AUTHPRIV syslog facility.
45    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    // `doas` calls pam_setcred(), if this is not defined to succeed, it prints
62    // a fabulous `doas: pam_setcred(?, PAM_REINITIALIZE_CRED): Permission denied: Unknown error -3`
63    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        // It is not an error if this variable is not present, just continue down the function
115        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}