Skip to main content

pam_sys2/
lib.rs

1//! FFI wrappers for the Linux Pluggable Authentication Modules (PAM)
2//!
3//! This crate provides raw access to the Linux-PAM API.
4//! Constants, types and functions are supported and created with `bindgen`.
5//!
6//! Note: Currently only tested on Linux as I lack access to other OSes at
7//! the moment. Both `build.rs` and `wrapper.h` probably need to be customized
8//! to exclude missing libraries such as `pam_misc` when they are not present.
9
10#![allow(
11    non_upper_case_globals,
12    non_camel_case_types,
13    non_snake_case,
14    deref_nullptr
15)]
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum PamImplementation {
19    LinuxPAM,
20    OpenPAM,
21}
22
23pub const PAM_IMPLEMENTATION: PamImplementation = {
24    #[cfg(PAM_SYS_IMPL = "linux-pam")]
25    {
26        PamImplementation::LinuxPAM
27    }
28
29    #[cfg(PAM_SYS_IMPL = "openpam")]
30    {
31        PamImplementation::OpenPAM
32    }
33
34    #[cfg(not(any(PAM_SYS_IMPL = "linux-pam", PAM_SYS_IMPL = "openpam",)))]
35    compile_error!("No valid PAM implementation selected")
36};
37
38#[cfg(all(PAM_SYS_IMPL = "linux-pam", feature = "generate-bindings"))]
39pub mod linuxpam {
40    include!(concat!(env!("OUT_DIR"), "/linuxpam.rs"));
41}
42
43#[cfg(all(
44    PAM_SYS_IMPL = "linux-pam", // docs works only on Linux
45    not(feature = "generate-bindings"),
46))]
47pub mod linuxpam;
48
49#[cfg(all(any(doc, PAM_SYS_IMPL = "openpam"), feature = "generate-bindings"))]
50pub mod openpam {
51    include!(concat!(env!("OUT_DIR"), "/openpam.rs"));
52}
53
54#[cfg(all(any(doc, PAM_SYS_IMPL = "openpam"), not(feature = "generate-bindings")))]
55pub mod openpam;
56
57#[cfg(PAM_SYS_IMPL = "linux-pam")]
58pub use linuxpam::*;
59
60#[cfg(PAM_SYS_IMPL = "openpam")]
61pub use openpam::*;
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    #[test]
67    fn test_pam_implementation() {
68        match PAM_IMPLEMENTATION {
69            PamImplementation::LinuxPAM => {
70                assert!(cfg!(PAM_SYS_IMPL = "linux-pam"));
71            }
72            PamImplementation::OpenPAM => {
73                assert!(cfg!(PAM_SYS_IMPL = "openpam"));
74            }
75        }
76    }
77
78    #[test]
79    #[cfg(PAM_SYS_IMPL = "linux-pam")]
80    fn test_linuxpam_is_working() {
81        unsafe {
82            match PAM_IMPLEMENTATION {
83                PamImplementation::LinuxPAM => {
84                    use std::ffi::CString;
85                    let service = CString::new("test_service").unwrap();
86                    let user = CString::new("test_user").unwrap();
87
88                    // Create a minimal conversation structure
89                    extern "C" fn conv_func(
90                        _num_msg: ::std::os::raw::c_int,
91                        _msg: *mut *const pam_message,
92                        _resp: *mut *mut pam_response,
93                        _appdata_ptr: *mut ::std::os::raw::c_void,
94                    ) -> ::std::os::raw::c_int {
95                        PAM_SUCCESS
96                    }
97
98                    let conv = pam_conv {
99                        conv: Some(conv_func),
100                        appdata_ptr: std::ptr::null_mut(),
101                    };
102
103                    let mut pamh: *mut pam_handle_t = std::ptr::null_mut();
104                    assert_eq!(
105                        pam_start(service.as_ptr(), user.as_ptr(), &conv, &mut pamh),
106                        PAM_SUCCESS
107                    );
108                    assert!(!pamh.is_null());
109                    assert_eq!(pam_end(pamh, PAM_SUCCESS), PAM_SUCCESS);
110                }
111                PamImplementation::OpenPAM => {
112                    panic!("pam_sys is not configured for OpenPAM");
113                }
114            }
115        }
116    }
117
118    #[test]
119    #[cfg(PAM_SYS_IMPL = "openpam")]
120    fn test_openpam_is_working() {
121        unsafe {
122            match PAM_IMPLEMENTATION {
123                PamImplementation::OpenPAM => {
124                    use std::ffi::CString;
125                    let service = CString::new("test_service").unwrap();
126                    let user = CString::new("test_user").unwrap();
127
128                    // Create a minimal conversation structure
129                    unsafe extern "C" fn conv_func(
130                        _num_msg: ::std::os::raw::c_int,
131                        _msg: *mut *const pam_message,
132                        _resp: *mut *mut pam_response,
133                        _appdata_ptr: *mut ::std::os::raw::c_void,
134                    ) -> ::std::os::raw::c_int {
135                        PAM_SUCCESS
136                    }
137
138                    let conv = pam_conv {
139                        conv: Some(conv_func),
140                        appdata_ptr: std::ptr::null_mut(),
141                    };
142
143                    let mut pamh: *mut pam_handle_t = std::ptr::null_mut();
144                    assert_eq!(
145                        pam_start(service.as_ptr(), user.as_ptr(), &conv, &mut pamh),
146                        PAM_SUCCESS
147                    );
148                    assert!(!pamh.is_null());
149                    assert_eq!(pam_end(pamh, PAM_SUCCESS), PAM_SUCCESS);
150                }
151                PamImplementation::LinuxPAM => {
152                    panic!("pam_sys is not configured for LinuxPAM");
153                }
154            }
155        }
156    }
157}