Mercurial > crates > nonstick
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 } |