pam_ssh_agent/
lib.rs

1mod agent;
2mod args;
3mod auth;
4mod expansions;
5mod logging;
6#[cfg(feature = "native-crypto")]
7mod nativecrypto;
8mod verify;
9
10pub use crate::agent::SSHAgent;
11pub use crate::auth::authenticate;
12use pam::constants::{PamFlag, PamResultCode};
13use pam::module::{PamHandle, PamHooks};
14use std::env;
15use std::env::VarError;
16
17use crate::expansions::UnixEnvironment;
18use crate::logging::init_logging;
19use anyhow::{anyhow, Result};
20use args::Args;
21use log::{error, info};
22use pam::items::{Service, User};
23use ssh_agent_client_rs::Client;
24use std::ffi::CStr;
25use std::path::Path;
26
27struct PamSshAgent;
28pam::pam_hooks!(PamSshAgent);
29
30impl PamHooks for PamSshAgent {
31    /// The authentication method called by pam to authenticate the user. This method
32    /// will return PAM_SUCCESS if the ssh-agent available through the unix socket path
33    /// in the PAM_AUTH_SOCK environment variable is able to correctly sign a random
34    /// message with the private key corresponding to one of the public keys in
35    /// /etc/security/authorized_key. Otherwise, this function returns PAM_AUTH_ERR.
36    ///
37    /// This method logs diagnostic output to the AUTHPRIV syslog facility.
38    fn sm_authenticate(
39        pam_handle: &mut PamHandle,
40        args: Vec<&CStr>,
41        _flags: PamFlag,
42    ) -> PamResultCode {
43        match run(args, pam_handle) {
44            Ok(_) => PamResultCode::PAM_SUCCESS,
45            Err(err) => {
46                for line in format!("{err:?}").split('\n') {
47                    error!("{line}")
48                }
49                PamResultCode::PAM_AUTH_ERR
50            }
51        }
52    }
53
54    // `doas` calls pam_setcred(), if this is not defined to succeed, it prints
55    // a fabulous `doas: pam_setcred(?, PAM_REINITIALIZE_CRED): Permission denied: Unknown error -3`
56    fn sm_setcred(
57        _pam_handle: &mut PamHandle,
58        _args: Vec<&CStr>,
59        _flags: PamFlag,
60    ) -> PamResultCode {
61        PamResultCode::PAM_SUCCESS
62    }
63}
64
65fn run(args: Vec<&CStr>, pam_handle: &PamHandle) -> Result<()> {
66    init_logging(get_service(pam_handle))?;
67    let args = Args::parse(args, Some(&UnixEnvironment::new(pam_handle)))?;
68    if args.debug {
69        log::set_max_level(log::LevelFilter::Debug);
70    }
71    do_authenticate(&args, pam_handle)?;
72    Ok(())
73}
74
75fn do_authenticate(args: &Args, handle: &PamHandle) -> Result<()> {
76    let path = get_path(args)?;
77    info!(
78        "Authenticating using ssh-agent at '{}' and keys from '{}'",
79        path, args.file
80    );
81    let ssh_agent_client = Client::connect(Path::new(path.as_str()))?;
82    match authenticate(
83        args.file.as_str(),
84        args.ca_keys_file.as_deref(),
85        ssh_agent_client,
86        &get_user(handle)?,
87    )? {
88        true => Ok(()),
89        false => Err(anyhow!("Agent did not know of any of the allowed keys")),
90    }
91}
92
93fn get_path(args: &Args) -> Result<String> {
94    match env::var("SSH_AUTH_SOCK") {
95        Ok(path) => return Ok(path),
96        // It is not an error if this variable is not present, just continue down the function
97        Err(VarError::NotPresent) => {}
98        Err(_) => {
99            return Err(anyhow!("Failed to read environment variable SSH_AUTH_SOCK"));
100        }
101    }
102    match &args.default_ssh_auth_sock {
103        Some(path) => Ok(path.to_string()),
104        None => Err(anyhow!(
105            "SSH_AUTH_SOCK not set and the default_ssh_auth_sock parameter is not set"
106        )),
107    }
108}
109
110/// Fetch the name of the current service, i.e. the software that uses pam for authentication
111/// using the PamHandle::get_item() method.
112fn get_service(pam_handle: &PamHandle) -> String {
113    let service = match pam_handle.get_item::<Service>() {
114        Ok(Some(service)) => service,
115        _ => return "unknown".into(),
116    };
117    String::from_utf8_lossy(service.0.to_bytes()).to_string()
118}
119
120/// Fetch the name of the current service, i.e. the software that uses pam for authentication
121/// using the PamHandle::get_item() method.
122fn get_user(pam_handle: &PamHandle) -> Result<String> {
123    let service = match pam_handle.get_item::<User>() {
124        Ok(Some(user)) => user,
125        _ => return Err(anyhow!("Failed to get user")),
126    };
127    Ok(String::from_utf8_lossy(service.0.to_bytes()).to_string())
128}