comparison src/libpam/handle.rs @ 80:5aa1a010f1e8

Start using PAM headers; improve owned/borrowed distinction. - Uses bindgen to generate bindings (only if needed). - Gets the story together on owned vs. borrowed handles. - Reduces number of mutable borrows in handle operation (since `PamHandle` is neither `Send` nor `Sync`, we never have to worry about thread safety. - Improves a bunch of macros so we don't have our own special syntax for docs. - Implement question indirection for standard XSSO PAM implementations.
author Paul Fisher <paul@pfish.zone>
date Tue, 10 Jun 2025 01:09:30 -0400
parents 002adfb98c5c
children
comparison
equal deleted inserted replaced
79:2128123b9406 80:5aa1a010f1e8
1 use super::conversation::LibPamConversation; 1 use super::conversation::LibPamConversation;
2 use crate::constants::{ErrorCode, InvalidEnum, Result}; 2 use crate::constants::{ErrorCode, Result};
3 use crate::conv::Message; 3 use crate::conv::Message;
4 use crate::handle::{PamApplicationOnly, PamModuleOnly, PamShared}; 4 use crate::handle::PamShared;
5 use crate::libpam::memory; 5 pub use crate::libpam::pam_ffi::LibPamHandle;
6 use crate::libpam::memory::Immovable; 6 use crate::libpam::{memory, pam_ffi};
7 use crate::Conversation; 7 use crate::{Conversation, PamHandleModule};
8 use num_derive::FromPrimitive; 8 use num_enum::{IntoPrimitive, TryFromPrimitive};
9 use num_traits::FromPrimitive; 9 use std::cell::Cell;
10 use std::ffi::{c_char, c_int}; 10 use std::ffi::{c_char, c_int};
11 use std::ops::{Deref, DerefMut}; 11 use std::ops::{Deref, DerefMut};
12 use std::result::Result as StdResult; 12 use std::ptr;
13 use std::{mem, ptr}; 13
14 14 struct HandleWrap(*mut LibPamHandle);
15 /// An owned PAM handle. 15
16 #[repr(transparent)] 16 impl Deref for HandleWrap {
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; 17 type Target = LibPamHandle;
83 fn deref(&self) -> &Self::Target { 18 fn deref(&self) -> &Self::Target {
84 unsafe { &*self.0 } 19 unsafe { &*self.0 }
85 } 20 }
86 } 21 }
87 22
88 impl DerefMut for OwnedLibPamHandle { 23 impl DerefMut for HandleWrap {
89 fn deref_mut(&mut self) -> &mut Self::Target { 24 fn deref_mut(&mut self) -> &mut Self::Target {
90 unsafe { &mut *self.0 } 25 unsafe { &mut *self.0 }
91 } 26 }
92 } 27 }
93 28
29 /// An owned PAM handle.
30 pub struct OwnedLibPamHandle {
31 handle: HandleWrap,
32 last_return: Cell<Result<()>>,
33 }
34
35 // TODO: pam_authenticate - app
36 // pam_setcred - app
37 // pam_acct_mgmt - app
38 // pam_chauthtok - app
39 // pam_open_session - app
40 // pam_close_session - app
41 // pam_putenv - shared
42 // pam_getenv - shared
43 // pam_getenvlist - shared
44
94 impl Drop for OwnedLibPamHandle { 45 impl Drop for OwnedLibPamHandle {
95 /// Ends the PAM session with a zero error code. 46 /// Closes the PAM session on an owned PAM handle.
96 /// You probably want to call [`close`](Self::close) instead of 47 ///
97 /// letting this drop by itself. 48 /// See the [`pam_end` manual page][man] for more information.
49 ///
50 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html
98 fn drop(&mut self) { 51 fn drop(&mut self) {
99 unsafe { 52 unsafe {
100 super::pam_end(self.0, 0); 53 pam_ffi::pam_end(
54 self.handle.0,
55 ErrorCode::result_to_c(self.last_return.get()),
56 );
101 } 57 }
102 } 58 }
103 } 59 }
104 60
105 macro_rules! cstr_item { 61 macro_rules! cstr_item {
106 (get = $getter:ident, item = $item_type:path) => { 62 (get = $getter:ident, item = $item_type:path) => {
107 fn $getter(&mut self) -> Result<Option<&str>> { 63 fn $getter(&self) -> Result<Option<&str>> {
108 unsafe { self.get_cstr_item($item_type) } 64 unsafe { self.get_cstr_item($item_type) }
109 } 65 }
110 }; 66 };
111 (set = $setter:ident, item = $item_type:path) => { 67 (set = $setter:ident, item = $item_type:path) => {
112 fn $setter(&mut self, value: Option<&str>) -> Result<()> { 68 fn $setter(&mut self, value: Option<&str>) -> Result<()> {
117 73
118 impl PamShared for LibPamHandle { 74 impl PamShared for LibPamHandle {
119 fn get_user(&mut self, prompt: Option<&str>) -> Result<&str> { 75 fn get_user(&mut self, prompt: Option<&str>) -> Result<&str> {
120 let prompt = memory::option_cstr(prompt)?; 76 let prompt = memory::option_cstr(prompt)?;
121 let mut output: *const c_char = ptr::null(); 77 let mut output: *const c_char = ptr::null();
122 let ret = 78 let ret = unsafe {
123 unsafe { super::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref())) }; 79 pam_ffi::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref()))
80 };
124 ErrorCode::result_from(ret)?; 81 ErrorCode::result_from(ret)?;
125 unsafe { memory::wrap_string(output) } 82 unsafe { memory::wrap_string(output) }
126 .transpose() 83 .transpose()
127 .unwrap_or(Err(ErrorCode::ConversationError)) 84 .unwrap_or(Err(ErrorCode::ConversationError))
128 } 85 }
154 } 111 }
155 } 112 }
156 } 113 }
157 } 114 }
158 115
159 impl PamModuleOnly for LibPamHandle { 116 impl PamHandleModule for LibPamHandle {
160 fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str> { 117 fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str> {
161 let prompt = memory::option_cstr(prompt)?; 118 let prompt = memory::option_cstr(prompt)?;
162 let mut output: *const c_char = ptr::null_mut(); 119 let mut output: *const c_char = ptr::null_mut();
163 // SAFETY: We're calling this with known-good values. 120 // SAFETY: We're calling this with known-good values.
164 let res = unsafe { 121 let res = unsafe {
165 super::pam_get_authtok( 122 pam_ffi::pam_get_authtok(
166 self, 123 self,
167 ItemType::AuthTok.into(), 124 ItemType::AuthTok.into(),
168 &mut output, 125 &mut output,
169 memory::prompt_ptr(prompt.as_ref()), 126 memory::prompt_ptr(prompt.as_ref()),
170 ) 127 )
188 unsafe { 145 unsafe {
189 let _data: Box<T> = Box::from_raw(c_data.cast()); 146 let _data: Box<T> = Box::from_raw(c_data.cast());
190 } 147 }
191 } 148 }
192 149
150 impl LibPamHandle {
151 /// Gets a C string item.
152 ///
153 /// # Safety
154 ///
155 /// You better be requesting an item which is a C string.
156 unsafe fn get_cstr_item(&self, item_type: ItemType) -> Result<Option<&str>> {
157 let mut output = ptr::null();
158 let ret = unsafe { pam_ffi::pam_get_item(self, item_type as c_int, &mut output) };
159 ErrorCode::result_from(ret)?;
160 memory::wrap_string(output.cast())
161 }
162
163 /// Sets a C string item.
164 ///
165 /// # Safety
166 ///
167 /// You better be setting an item which is a C string.
168 unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> {
169 let data_str = memory::option_cstr(data)?;
170 let ret = unsafe {
171 pam_ffi::pam_set_item(
172 self,
173 item_type as c_int,
174 memory::prompt_ptr(data_str.as_ref()).cast(),
175 )
176 };
177 ErrorCode::result_from(ret)
178 }
179
180 /// Gets the `PAM_CONV` item from the handle.
181 fn conversation_item(&mut self) -> Result<&mut LibPamConversation<'_>> {
182 let output: *mut LibPamConversation = ptr::null_mut();
183 let result = unsafe {
184 pam_ffi::pam_get_item(
185 self,
186 ItemType::Conversation.into(),
187 &mut output.cast_const().cast(),
188 )
189 };
190 ErrorCode::result_from(result)?;
191 // SAFETY: We got this result from PAM, and we're checking if it's null.
192 unsafe { output.as_mut() }.ok_or(ErrorCode::ConversationError)
193 }
194 }
195
196 macro_rules! delegate {
197 (fn $meth:ident(&self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => {
198 fn $meth(&self $(, $param: $typ)*) -> Result<$ret> {
199 let result = self.handle.$meth($($param),*);
200 self.last_return.set(split(&result));
201 result
202 }
203 };
204 (fn $meth:ident(&mut self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => {
205 fn $meth(&mut self $(, $param: $typ)*) -> Result<$ret> {
206 let result = self.handle.$meth($($param),*);
207 self.last_return.set(split(&result));
208 result
209 }
210 };
211 (get = $get:ident$(, set = $set:ident)?) => {
212 delegate!(fn $get(&self) -> Result<Option<&str>>);
213 $(delegate!(set = $set);)?
214 };
215 (set = $set:ident) => {
216 delegate!(fn $set(&mut self, value: Option<&str>) -> Result<()>);
217 };
218 }
219
220 fn split<T>(result: &Result<T>) -> Result<()> {
221 result.as_ref().map(drop).map_err(|&e| e)
222 }
223
224 impl PamShared for OwnedLibPamHandle {
225 delegate!(fn get_user(&mut self, prompt: Option<&str>) -> Result<&str>);
226 delegate!(get = user_item, set = set_user_item);
227 delegate!(get = service, set = set_service);
228 delegate!(get = user_prompt, set = set_user_prompt);
229 delegate!(get = tty_name, set = set_tty_name);
230 delegate!(get = remote_user, set = set_remote_user);
231 delegate!(get = remote_host, set = set_remote_host);
232 delegate!(set = set_authtok_item);
233 delegate!(set = set_old_authtok_item);
234 }
235
193 /// Identifies what is being gotten or set with `pam_get_item` 236 /// Identifies what is being gotten or set with `pam_get_item`
194 /// or `pam_set_item`. 237 /// or `pam_set_item`.
195 #[derive(FromPrimitive)] 238 #[derive(TryFromPrimitive, IntoPrimitive)]
196 #[repr(i32)] 239 #[repr(i32)]
197 #[non_exhaustive] // because C could give us anything! 240 #[non_exhaustive] // because C could give us anything!
198 pub enum ItemType { 241 pub enum ItemType {
199 /// The PAM service name. 242 /// The PAM service name.
200 Service = 1, 243 Service = 1,
221 /// X server authentication data. 264 /// X server authentication data.
222 XAuthData = 12, 265 XAuthData = 12,
223 /// The type of `pam_get_authtok`. 266 /// The type of `pam_get_authtok`.
224 AuthTokType = 13, 267 AuthTokType = 13,
225 } 268 }
226
227 impl TryFrom<c_int> for ItemType {
228 type Error = InvalidEnum<Self>;
229 fn try_from(value: c_int) -> StdResult<Self, Self::Error> {
230 Self::from_i32(value).ok_or(value.into())
231 }
232 }
233
234 impl From<ItemType> for c_int {
235 fn from(val: ItemType) -> Self {
236 val as Self
237 }
238 }