comparison src/pam_ffi/handle.rs @ 73:ac6881304c78

Do conversations, along with way too much stuff. This implements conversations, along with all the memory management brouhaha that goes along with it. The conversation now lives directly on the handle rather than being a thing you have to get from it and then call manually. It Turns Out this makes things a lot easier! I guess we reorganized things again. For the last time. For real. I promise. This all passes ASAN, so it seems Pretty Good!
author Paul Fisher <paul@pfish.zone>
date Thu, 05 Jun 2025 03:41:38 -0400
parents
children c7c596e6388f
comparison
equal deleted inserted replaced
72:47eb242a4f88 73:ac6881304c78
1 use super::conversation::LibPamConversation;
2 use crate::constants::{ErrorCode, InvalidEnum, Result};
3 use crate::conv::Message;
4 use crate::handle::{PamApplicationOnly, PamModuleOnly, PamShared};
5 use crate::pam_ffi::memory;
6 use crate::pam_ffi::memory::Immovable;
7 use crate::{Conversation, Response};
8 use num_derive::FromPrimitive;
9 use num_traits::FromPrimitive;
10 use std::ffi::{c_char, c_int};
11 use std::ops::{Deref, DerefMut};
12 use std::result::Result as StdResult;
13 use std::{mem, ptr};
14
15 /// An owned PAM handle.
16 #[repr(transparent)]
17 pub struct OwnedLibPamHandle(*mut LibPamHandle);
18
19 /// An opaque structure that a PAM handle points to.
20 #[repr(C)]
21 pub struct LibPamHandle {
22 _data: (),
23 _marker: Immovable,
24 }
25
26 impl LibPamHandle {
27 /// Gets a C string item.
28 ///
29 /// # Safety
30 ///
31 /// You better be requesting an item which is a C string.
32 unsafe fn get_cstr_item(&mut self, item_type: ItemType) -> Result<Option<&str>> {
33 let mut output = ptr::null();
34 let ret = unsafe { super::pam_get_item(self, item_type as c_int, &mut output) };
35 ErrorCode::result_from(ret)?;
36 memory::wrap_string(output.cast())
37 }
38
39 /// Sets a C string item.
40 ///
41 /// # Safety
42 ///
43 /// You better be setting an item which is a C string.
44 unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> {
45 let data_str = memory::option_cstr(data)?;
46 let ret = unsafe {
47 super::pam_set_item(
48 self,
49 item_type as c_int,
50 memory::prompt_ptr(data_str.as_ref()).cast(),
51 )
52 };
53 ErrorCode::result_from(ret)
54 }
55
56 /// Gets the `PAM_CONV` item from the handle.
57 fn conversation_item(&mut self) -> Result<&mut LibPamConversation> {
58 let output: *mut LibPamConversation = ptr::null_mut();
59 let result = unsafe {
60 super::pam_get_item(
61 self,
62 ItemType::Conversation.into(),
63 &mut output.cast_const().cast(),
64 )
65 };
66 ErrorCode::result_from(result)?;
67 // SAFETY: We got this result from PAM, and we're checking if it's null.
68 unsafe { output.as_mut() }.ok_or(ErrorCode::ConversationError)
69 }
70 }
71
72 impl PamApplicationOnly for OwnedLibPamHandle {
73 fn close(self, status: Result<()>) -> Result<()> {
74 let ret = unsafe { super::pam_end(self.0, ErrorCode::result_to_c(status)) };
75 // Forget rather than dropping, since dropping also calls pam_end.
76 mem::forget(self);
77 ErrorCode::result_from(ret)
78 }
79 }
80
81 impl Deref for OwnedLibPamHandle {
82 type Target = LibPamHandle;
83 fn deref(&self) -> &Self::Target {
84 unsafe { &*self.0 }
85 }
86 }
87
88 impl DerefMut for OwnedLibPamHandle {
89 fn deref_mut(&mut self) -> &mut Self::Target {
90 unsafe { &mut *self.0 }
91 }
92 }
93
94 impl Drop for OwnedLibPamHandle {
95 /// Ends the PAM session with a zero error code.
96 /// You probably want to call [`close`](Self::close) instead of
97 /// letting this drop by itself.
98 fn drop(&mut self) {
99 unsafe {
100 super::pam_end(self.0, 0);
101 }
102 }
103 }
104
105 macro_rules! cstr_item {
106 (get = $getter:ident, item = $item_type:path) => {
107 fn $getter(&mut self) -> Result<Option<&str>> {
108 unsafe { self.get_cstr_item($item_type) }
109 }
110 };
111 (set = $setter:ident, item = $item_type:path) => {
112 fn $setter(&mut self, value: Option<&str>) -> Result<()> {
113 unsafe { self.set_cstr_item($item_type, value) }
114 }
115 };
116 }
117
118 impl PamShared for LibPamHandle {
119 fn get_user(&mut self, prompt: Option<&str>) -> Result<&str> {
120 let prompt = memory::option_cstr(prompt)?;
121 let mut output: *const c_char = ptr::null();
122 let ret =
123 unsafe { super::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref())) };
124 ErrorCode::result_from(ret)?;
125 unsafe { memory::wrap_string(output) }
126 .transpose()
127 .unwrap_or(Err(ErrorCode::ConversationError))
128 }
129
130 cstr_item!(get = user_item, item = ItemType::User);
131 cstr_item!(set = set_user_item, item = ItemType::User);
132 cstr_item!(get = service, item = ItemType::Service);
133 cstr_item!(set = set_service, item = ItemType::Service);
134 cstr_item!(get = user_prompt, item = ItemType::UserPrompt);
135 cstr_item!(set = set_user_prompt, item = ItemType::UserPrompt);
136 cstr_item!(get = tty_name, item = ItemType::Tty);
137 cstr_item!(set = set_tty_name, item = ItemType::Tty);
138 cstr_item!(get = remote_user, item = ItemType::RemoteUser);
139 cstr_item!(set = set_remote_user, item = ItemType::RemoteUser);
140 cstr_item!(get = remote_host, item = ItemType::RemoteHost);
141 cstr_item!(set = set_remote_host, item = ItemType::RemoteHost);
142 cstr_item!(set = set_authtok_item, item = ItemType::AuthTok);
143 cstr_item!(set = set_old_authtok_item, item = ItemType::OldAuthTok);
144 }
145
146 impl Conversation for LibPamHandle {
147 fn converse(&mut self, messages: &[Message]) -> Result<Vec<Response>> {
148 self.conversation_item()?.converse(messages)
149 }
150 }
151
152 impl PamModuleOnly for LibPamHandle {
153 fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str> {
154 let prompt = memory::option_cstr(prompt)?;
155 let mut output: *const c_char = ptr::null_mut();
156 // SAFETY: We're calling this with known-good values.
157 let res = unsafe {
158 super::pam_get_authtok(
159 self,
160 ItemType::AuthTok.into(),
161 &mut output,
162 memory::prompt_ptr(prompt.as_ref()),
163 )
164 };
165 ErrorCode::result_from(res)?;
166 // SAFETY: We got this string from PAM.
167 unsafe { memory::wrap_string(output) }
168 .transpose()
169 .unwrap_or(Err(ErrorCode::ConversationError))
170 }
171
172 cstr_item!(get = authtok_item, item = ItemType::AuthTok);
173 cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok);
174 }
175
176 /// Function called at the end of a PAM session that is called to clean up
177 /// a value previously provided to PAM in a `pam_set_data` call.
178 ///
179 /// You should never call this yourself.
180 extern "C" fn set_data_cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) {
181 unsafe {
182 let _data: Box<T> = Box::from_raw(c_data.cast());
183 }
184 }
185
186 /// Identifies what is being gotten or set with `pam_get_item`
187 /// or `pam_set_item`.
188 #[derive(FromPrimitive)]
189 #[repr(i32)]
190 #[non_exhaustive] // because C could give us anything!
191 pub enum ItemType {
192 /// The PAM service name.
193 Service = 1,
194 /// The user's login name.
195 User = 2,
196 /// The TTY name.
197 Tty = 3,
198 /// The remote host (if applicable).
199 RemoteHost = 4,
200 /// The conversation struct (not a CStr-based item).
201 Conversation = 5,
202 /// The authentication token (password).
203 AuthTok = 6,
204 /// The old authentication token (when changing passwords).
205 OldAuthTok = 7,
206 /// The remote user's name.
207 RemoteUser = 8,
208 /// The prompt shown when requesting a username.
209 UserPrompt = 9,
210 /// App-supplied function to override failure delays.
211 FailDelay = 10,
212 /// X display name.
213 XDisplay = 11,
214 /// X server authentication data.
215 XAuthData = 12,
216 /// The type of `pam_get_authtok`.
217 AuthTokType = 13,
218 }
219
220 impl TryFrom<c_int> for ItemType {
221 type Error = InvalidEnum<Self>;
222 fn try_from(value: c_int) -> StdResult<Self, Self::Error> {
223 Self::from_i32(value).ok_or(value.into())
224 }
225 }
226
227 impl From<ItemType> for c_int {
228 fn from(val: ItemType) -> Self {
229 val as Self
230 }
231 }