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 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 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 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
110fn 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
120fn 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}