comparison src/pam_ffi/module.rs @ 74:c7c596e6388f

Make conversations type-safe (last big reorg) (REAL) (NOT CLICKBAIT) In previous versions of Conversation, you could send messages and then return messages of the wrong type or in the wrong order or whatever. The receiver would then have to make sure that there were the right number of messages and that each message was the right type. That's annoying. This change makes the `Message` enum a two-way channel, where the asker puts their question into it, and then the answerer (the conversation) puts the answer in and returns control to the asker. The asker then only has to pull the Answer of the type they wanted out of the message.
author Paul Fisher <paul@pfish.zone>
date Fri, 06 Jun 2025 22:21:17 -0400
parents src/module.rs@ac6881304c78
children
comparison
equal deleted inserted replaced
73:ac6881304c78 74:c7c596e6388f
1 use std::ffi::CStr;
2
3 /// Generates the dynamic library entry points for a [PamModule] implementation.
4 ///
5 /// Calling `pam_hooks!(SomeType)` on a type that implements [PamModule] will
6 /// generate the exported `extern "C"` functions that PAM uses to call into
7 /// your module.
8 ///
9 /// ## Examples:
10 ///
11 /// Here is full example of a PAM module that would authenticate and authorize everybody:
12 ///
13 /// ```
14 /// use nonstick::{Flags, OwnedLibPamHandle, PamModule, PamHandleModule, Result as PamResult, pam_hooks};
15 /// use std::ffi::CStr;
16 /// # fn main() {}
17 ///
18 /// struct MyPamModule;
19 /// pam_hooks!(MyPamModule);
20 ///
21 /// impl<T: PamHandleModule> PamModule<T> for MyPamModule {
22 /// fn authenticate(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
23 /// let password = handle.get_authtok(Some("what's your password?"))?;
24 /// handle.info_msg(fmt!("If you say your password is {password:?}, who am I to disagree?"));
25 /// }
26 ///
27 /// fn account_management(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
28 /// let username = handle.get_user(None)?;
29 /// handle.info_msg(fmt!("Hello {username}! I trust you unconditionally."))
30 /// Ok(())
31 /// }
32 /// }
33 /// ```
34 #[macro_export]
35 macro_rules! pam_hooks {
36 ($ident:ident) => {
37 mod _pam_hooks_scope {
38 use std::ffi::{c_char, c_int, CStr};
39 use $crate::{ErrorCode, Flags, LibPamHandle, PamModule};
40
41 #[no_mangle]
42 extern "C" fn pam_sm_acct_mgmt(
43 pamh: *mut libc::c_void,
44 flags: Flags,
45 argc: c_int,
46 argv: *const *const c_char,
47 ) -> c_int {
48 if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
49 let args = extract_argv(argc, argv);
50 ErrorCode::result_to_c(super::$ident::account_management(handle, args, flags))
51 } else {
52 ErrorCode::Ignore as c_int
53 }
54 }
55
56 #[no_mangle]
57 extern "C" fn pam_sm_authenticate(
58 pamh: *mut libc::c_void,
59 flags: Flags,
60 argc: c_int,
61 argv: *const *const c_char,
62 ) -> c_int {
63 if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
64 let args = extract_argv(argc, argv);
65 ErrorCode::result_to_c(super::$ident::authenticate(handle, args, flags))
66 } else {
67 ErrorCode::Ignore as c_int
68 }
69 }
70
71 #[no_mangle]
72 extern "C" fn pam_sm_chauthtok(
73 pamh: *mut libc::c_void,
74 flags: Flags,
75 argc: c_int,
76 argv: *const *const c_char,
77 ) -> c_int {
78 if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
79 let args = extract_argv(argc, argv);
80 ErrorCode::result_to_c(super::$ident::change_authtok(handle, args, flags))
81 } else {
82 ErrorCode::Ignore as c_int
83 }
84 }
85
86 #[no_mangle]
87 extern "C" fn pam_sm_close_session(
88 pamh: *mut libc::c_void,
89 flags: Flags,
90 argc: c_int,
91 argv: *const *const c_char,
92 ) -> c_int {
93 if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
94 let args = extract_argv(argc, argv);
95 ErrorCode::result_to_c(super::$ident::close_session(handle, args, flags))
96 } else {
97 ErrorCode::Ignore as c_int
98 }
99 }
100
101 #[no_mangle]
102 extern "C" fn pam_sm_open_session(
103 pamh: *mut libc::c_void,
104 flags: Flags,
105 argc: c_int,
106 argv: *const *const c_char,
107 ) -> c_int {
108 let args = extract_argv(argc, argv);
109 if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
110 ErrorCode::result_to_c(super::$ident::open_session(handle, args, flags))
111 } else {
112 ErrorCode::Ignore as c_int
113 }
114 }
115
116 #[no_mangle]
117 extern "C" fn pam_sm_setcred(
118 pamh: *mut libc::c_void,
119 flags: Flags,
120 argc: c_int,
121 argv: *const *const c_char,
122 ) -> c_int {
123 let args = extract_argv(argc, argv);
124 if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
125 ErrorCode::result_to_c(super::$ident::set_credentials(handle, args, flags))
126 } else {
127 ErrorCode::Ignore as c_int
128 }
129 }
130
131 /// Turns `argc`/`argv` into a [Vec] of [CStr]s.
132 ///
133 /// # Safety
134 ///
135 /// We use this only with arguments we get from `libpam`, which we kind of have to trust.
136 fn extract_argv<'a>(argc: c_int, argv: *const *const c_char) -> Vec<&'a CStr> {
137 (0..argc)
138 .map(|o| unsafe { CStr::from_ptr(*argv.offset(o as isize) as *const c_char) })
139 .collect()
140 }
141 }
142 };
143 }
144
145 #[cfg(test)]
146 mod tests {
147 // Compile-time test that the `pam_hooks` macro compiles.
148 use crate::{PamHandleModule, PamModule};
149 struct Foo;
150 impl<T: PamHandleModule> PamModule<T> for Foo {}
151
152 pam_hooks!(Foo);
153 }