pam_ssh_agent/
lib.rs

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    /// The authentication method called by pam to authenticate the user. This method
27    /// will return PAM_SUCCESS if the ssh-agent available through the unix socket path
28    /// in the PAM_AUTH_SOCK environment variable is able to correctly sign a random
29    /// message with the private key corresponding to one of the public keys in in
30    /// /etc/security/authorized_key. Otherwise this function returns PAM_AUTH_ERR.
31    ///
32    /// This method logs diagnostic output to the AUTHPRIV facility.
33    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    // `doas` calls pam_setcred(), if this is not defined to succeed it prints
53    // a fabulous `doas: pam_setcred(?, PAM_REINITIALIZE_CRED): Permission denied: Unknown error -3`
54    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
77/// Fetch the name of the current service, i.e. the software that uses pam for authentication
78/// using the PamHandle::get_item() method.
79fn 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}