comparison src/module.rs @ 56:daa2cde64601

Big big refactor. Probably should have been multiple changes. - Makes FFI safer by explicitly specifying c_int in calls. - Uses ToPrimitive/FromPrimitive to make this easier. - Pulls PamFlag variables into a bitflags! struct. - Pulls PamMessageStyle variables into an enum. - Renames ResultCode to ErrorCode. - Switches from PAM_SUCCESS to using a Result<(), ErrorCode>. - Uses thiserror to make ErrorCode into an Error. - Gets rid of pam_try! because now we have Results. - Expands some names (e.g. Conv to Conversation). - Adds more doc comments. - Returns passwords as a SecureString, to avoid unnecessarily keeping it around in memory.
author Paul Fisher <paul@pfish.zone>
date Sun, 04 May 2025 02:56:55 -0400
parents 9d1160b02d2c
children 3f4a77aa88be
comparison
equal deleted inserted replaced
55:676675c3d434 56:daa2cde64601
1 //! Functions for use in pam modules. 1 //! Functions for use in pam modules.
2 2
3 use crate::constants::{PamFlag, PamResultCode}; 3 use crate::constants::{Flags, PamResult, ErrorCode};
4 use crate::items::{Item, ItemType}; 4 use crate::items::{Item, ItemType};
5 use libc::c_char; 5 use libc::c_char;
6 use std::ffi::{CStr, CString}; 6 use std::ffi::{c_int, CStr, CString};
7 use secure_string::SecureString;
7 8
8 /// Opaque type, used as a pointer when making pam API calls. 9 /// Opaque type, used as a pointer when making pam API calls.
9 /// 10 ///
10 /// A module is invoked via an external function such as `pam_sm_authenticate`. 11 /// A module is invoked via an external function such as `pam_sm_authenticate`.
11 /// Such a call provides a pam handle pointer. The same pointer should be given 12 /// Such a call provides a pam handle pointer. The same pointer should be given
19 extern "C" { 20 extern "C" {
20 fn pam_get_data( 21 fn pam_get_data(
21 pamh: *const PamHandle, 22 pamh: *const PamHandle,
22 module_data_name: *const c_char, 23 module_data_name: *const c_char,
23 data: &mut *const libc::c_void, 24 data: &mut *const libc::c_void,
24 ) -> PamResultCode; 25 ) -> c_int;
25 26
26 fn pam_set_data( 27 fn pam_set_data(
27 pamh: *const PamHandle, 28 pamh: *const PamHandle,
28 module_data_name: *const c_char, 29 module_data_name: *const c_char,
29 data: *mut libc::c_void, 30 data: *mut libc::c_void,
30 cleanup: extern "C" fn( 31 cleanup: extern "C" fn(
31 pamh: *const PamHandle, 32 pamh: *const PamHandle,
32 data: *mut libc::c_void, 33 data: *mut libc::c_void,
33 error_status: PamResultCode, 34 error_status: c_int,
34 ), 35 ),
35 ) -> PamResultCode; 36 ) -> c_int;
36 37
37 fn pam_get_item( 38 fn pam_get_item(
38 pamh: *const PamHandle, 39 pamh: *const PamHandle,
39 item_type: ItemType, 40 item_type: c_int,
40 item: &mut *const libc::c_void, 41 item: &mut *const libc::c_void,
41 ) -> PamResultCode; 42 ) -> c_int;
42 43
43 fn pam_set_item( 44 fn pam_set_item(pamh: *mut PamHandle, item_type: c_int, item: *const libc::c_void) -> c_int;
44 pamh: *mut PamHandle, 45
45 item_type: ItemType, 46 fn pam_get_user(pamh: *const PamHandle, user: &*mut c_char, prompt: *const c_char) -> c_int;
46 item: *const libc::c_void,
47 ) -> PamResultCode;
48
49 fn pam_get_user(
50 pamh: *const PamHandle,
51 user: &*mut c_char,
52 prompt: *const c_char,
53 ) -> PamResultCode;
54 47
55 fn pam_get_authtok( 48 fn pam_get_authtok(
56 pamh: *const PamHandle, 49 pamh: *const PamHandle,
57 item_type: ItemType, 50 item_type: c_int,
58 data: &*mut c_char, 51 data: &*mut c_char,
59 prompt: *const c_char, 52 prompt: *const c_char,
60 ) -> PamResultCode; 53 ) -> c_int;
61 54
62 } 55 }
63 56
64 pub extern "C" fn cleanup<T>(_: *const PamHandle, c_data: *mut libc::c_void, _: PamResultCode) { 57 /// Function called at the end of a PAM session that is called to clean up
58 /// a value previously provided to PAM in a `pam_set_data` call.
59 ///
60 /// You should never call this yourself.
61 extern "C" fn cleanup<T>(_: *const PamHandle, c_data: *mut libc::c_void, _: c_int) {
65 unsafe { 62 unsafe {
66 let _data: Box<T> = Box::from_raw(c_data.cast::<T>()); 63 let _data: Box<T> = Box::from_raw(c_data.cast::<T>());
67 } 64 }
68 } 65 }
69
70 pub type PamResult<T> = Result<T, PamResultCode>;
71 66
72 impl PamHandle { 67 impl PamHandle {
73 /// Gets some value, identified by `key`, that has been set by the module 68 /// Gets some value, identified by `key`, that has been set by the module
74 /// previously. 69 /// previously.
75 /// 70 ///
85 /// The data stored under the provided key must be of type `T` otherwise the 80 /// The data stored under the provided key must be of type `T` otherwise the
86 /// behaviour of this function is undefined. 81 /// behaviour of this function is undefined.
87 /// 82 ///
88 /// The data, if present, is owned by the current PAM conversation. 83 /// The data, if present, is owned by the current PAM conversation.
89 pub unsafe fn get_data<T>(&self, key: &str) -> PamResult<Option<&T>> { 84 pub unsafe fn get_data<T>(&self, key: &str) -> PamResult<Option<&T>> {
90 let c_key = CString::new(key).map_err(|_| PamResultCode::PAM_CONV_ERR)?; 85 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
91 let mut ptr: *const libc::c_void = std::ptr::null(); 86 let mut ptr: *const libc::c_void = std::ptr::null();
92 to_result(pam_get_data(self, c_key.as_ptr(), &mut ptr))?; 87 ErrorCode::result_from(pam_get_data(self, c_key.as_ptr(), &mut ptr))?;
93 match ptr.is_null() { 88 match ptr.is_null() {
94 true => Ok(None), 89 true => Ok(None),
95 false => { 90 false => {
96 let typed_ptr = ptr.cast::<T>(); 91 let typed_ptr = ptr.cast::<T>();
97 Ok(Some(&*typed_ptr)) 92 Ok(Some(&*typed_ptr))
107 /// 102 ///
108 /// # Errors 103 /// # Errors
109 /// 104 ///
110 /// Returns an error if the underlying PAM function call fails. 105 /// Returns an error if the underlying PAM function call fails.
111 pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> PamResult<()> { 106 pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> PamResult<()> {
112 let c_key = CString::new(key).map_err(|_| PamResultCode::PAM_CONV_ERR)?; 107 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
113 let res = unsafe { 108 let ret = unsafe {
114 pam_set_data( 109 pam_set_data(
115 self, 110 self,
116 c_key.as_ptr(), 111 c_key.as_ptr(),
117 Box::into_raw(data).cast::<libc::c_void>(), 112 Box::into_raw(data).cast::<libc::c_void>(),
118 cleanup::<T>, 113 cleanup::<T>,
119 ) 114 )
120 }; 115 };
121 to_result(res) 116 ErrorCode::result_from(ret)
122 } 117 }
123 118
124 /// Retrieves a value that has been set, possibly by the pam client. 119 /// Retrieves a value that has been set, possibly by the pam client.
125 /// This is particularly useful for getting a `PamConv` reference. 120 /// This is particularly useful for getting a `PamConv` reference.
126 /// 121 ///
134 /// 129 ///
135 /// Returns an error if the underlying PAM function call fails. 130 /// Returns an error if the underlying PAM function call fails.
136 pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> { 131 pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> {
137 let mut ptr: *const libc::c_void = std::ptr::null(); 132 let mut ptr: *const libc::c_void = std::ptr::null();
138 let out = unsafe { 133 let out = unsafe {
139 let r = pam_get_item(self, T::type_id(), &mut ptr); 134 let ret = pam_get_item(self, T::type_id().into(), &mut ptr);
140 to_result(r)?; 135 ErrorCode::result_from(ret)?;
141 let typed_ptr = ptr.cast::<T::Raw>(); 136 let typed_ptr = ptr.cast::<T::Raw>();
142 match typed_ptr.is_null() { 137 match typed_ptr.is_null() {
143 true => None, 138 true => None,
144 false => Some(T::from_raw(typed_ptr)), 139 false => Some(T::from_raw(typed_ptr)),
145 } 140 }
154 /// 149 ///
155 /// # Errors 150 /// # Errors
156 /// 151 ///
157 /// Returns an error if the underlying PAM function call fails. 152 /// Returns an error if the underlying PAM function call fails.
158 pub fn set_item<T: Item>(&mut self, item: T) -> PamResult<()> { 153 pub fn set_item<T: Item>(&mut self, item: T) -> PamResult<()> {
159 let res = 154 let ret =
160 unsafe { pam_set_item(self, T::type_id(), item.into_raw().cast::<libc::c_void>()) }; 155 unsafe { pam_set_item(self, T::type_id().into(), item.into_raw().cast::<libc::c_void>()) };
161 to_result(res) 156 ErrorCode::result_from(ret)
162 } 157 }
163 158
164 /// Retrieves the name of the user who is authenticating or logging in. 159 /// Retrieves the name of the user who is authenticating or logging in.
165 /// 160 ///
166 /// This is really a specialization of `get_item`. 161 /// This is really a specialization of `get_item`.
172 /// 167 ///
173 /// Returns an error if the underlying PAM function call fails. 168 /// Returns an error if the underlying PAM function call fails.
174 pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> { 169 pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> {
175 let prompt = option_cstr(prompt)?; 170 let prompt = option_cstr(prompt)?;
176 let output: *mut c_char = std::ptr::null_mut(); 171 let output: *mut c_char = std::ptr::null_mut();
177 let res = unsafe { pam_get_user(self, &output, prompt_ptr(prompt.as_ref())) }; 172 let ret = unsafe { pam_get_user(self, &output, prompt_ptr(prompt.as_ref())) };
178 match res { 173 ErrorCode::result_from(ret)?;
179 PamResultCode::PAM_SUCCESS => copy_pam_string(output), 174 copy_pam_string(output)
180 otherwise => Err(otherwise),
181 }
182 } 175 }
183 176
184 /// Retrieves the authentication token from the user. 177 /// Retrieves the authentication token from the user.
185 /// 178 ///
186 /// This is really a specialization of `get_item`. 179 /// This is really a specialization of `get_item`.
189 /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html). 182 /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html).
190 /// 183 ///
191 /// # Errors 184 /// # Errors
192 /// 185 ///
193 /// Returns an error if the underlying PAM function call fails. 186 /// Returns an error if the underlying PAM function call fails.
194 pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<String> { 187 pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<SecureString> {
195 let prompt = option_cstr(prompt)?; 188 let prompt = option_cstr(prompt)?;
196 let output: *mut c_char = std::ptr::null_mut(); 189 let output: *mut c_char = std::ptr::null_mut();
197 let res = unsafe { 190 let res = unsafe {
198 pam_get_authtok( 191 pam_get_authtok(
199 self, 192 self,
200 ItemType::AuthTok, 193 ItemType::AuthTok.into(),
201 &output, 194 &output,
202 prompt_ptr(prompt.as_ref()), 195 prompt_ptr(prompt.as_ref()),
203 ) 196 )
204 }; 197 };
205 to_result(res)?; 198 ErrorCode::result_from(res)?;
206 copy_pam_string(output) 199 copy_pam_string(output).map(SecureString::from)
207 } 200 }
208 } 201 }
209 202
210 /// Safely converts a `&str` option to a `CString` option. 203 /// Safely converts a `&str` option to a `CString` option.
211 fn option_cstr(prompt: Option<&str>) -> PamResult<Option<CString>> { 204 fn option_cstr(prompt: Option<&str>) -> PamResult<Option<CString>> {
212 prompt 205 prompt
213 .map(CString::new) 206 .map(CString::new)
214 .transpose() 207 .transpose()
215 .map_err(|_| PamResultCode::PAM_CONV_ERR) 208 .map_err(|_| ErrorCode::ConversationError)
216 } 209 }
217 210
218 /// The pointer to the prompt CString, or null if absent. 211 /// The pointer to the prompt CString, or null if absent.
219 fn prompt_ptr(prompt: Option<&CString>) -> *const c_char { 212 pub(crate) fn prompt_ptr(prompt: Option<&CString>) -> *const c_char {
220 match prompt { 213 match prompt {
221 Some(c_str) => c_str.as_ptr(), 214 Some(c_str) => c_str.as_ptr(),
222 None => std::ptr::null(), 215 None => std::ptr::null(),
223 } 216 }
224 } 217 }
225 218
226 /// Creates an owned copy of a string that is returned from a 219 /// Creates an owned copy of a string that is returned from a
227 /// <code>pam_get_<var>whatever</var></code> function. 220 /// <code>pam_get_<var>whatever</var></code> function.
228 fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> { 221 pub(crate) fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> {
229 // We really shouldn't get a null pointer back here, but if we do, return nothing. 222 // We really shouldn't get a null pointer back here, but if we do, return nothing.
230 if result_ptr.is_null() { 223 if result_ptr.is_null() {
231 return Ok(String::new()); 224 return Ok(String::new());
232 } 225 }
233 let bytes = unsafe { CStr::from_ptr(result_ptr) }; 226 let bytes = unsafe { CStr::from_ptr(result_ptr) };
234 Ok(bytes 227 bytes
235 .to_str() 228 .to_str()
236 .map_err(|_| PamResultCode::PAM_CONV_ERR)? 229 .map(String::from)
237 .into()) 230 .map_err(|_| ErrorCode::ConversationError)
238 }
239
240 /// Convenience to transform a `PamResultCode` into a unit `PamResult`.
241 fn to_result(result: PamResultCode) -> PamResult<()> {
242 match result {
243 PamResultCode::PAM_SUCCESS => Ok(()),
244 otherwise => Err(otherwise),
245 }
246 } 231 }
247 232
248 /// Provides functions that are invoked by the entrypoints generated by the 233 /// Provides functions that are invoked by the entrypoints generated by the
249 /// [`pam_hooks!` macro](../macro.pam_hooks.html). 234 /// [`pam_hooks!` macro](../macro.pam_hooks.html).
250 /// 235 ///
256 /// This function performs the task of establishing whether the user is permitted to gain access at 241 /// This function performs the task of establishing whether the user is permitted to gain access at
257 /// this time. It should be understood that the user has previously been validated by an 242 /// this time. It should be understood that the user has previously been validated by an
258 /// authentication module. This function checks for other things. Such things might be: the time of 243 /// authentication module. This function checks for other things. Such things might be: the time of
259 /// day or the date, the terminal line, remote hostname, etc. This function may also determine 244 /// day or the date, the terminal line, remote hostname, etc. This function may also determine
260 /// things like the expiration on passwords, and respond that the user change it before continuing. 245 /// things like the expiration on passwords, and respond that the user change it before continuing.
261 fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { 246 fn acct_mgmt(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
262 PamResultCode::PAM_IGNORE 247 Err(ErrorCode::Ignore)
263 } 248 }
264 249
265 /// This function performs the task of authenticating the user. 250 /// This function performs the task of authenticating the user.
266 fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { 251 fn sm_authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
267 PamResultCode::PAM_IGNORE 252 Err(ErrorCode::Ignore)
268 } 253 }
269 254
270 /// This function is used to (re-)set the authentication token of the user. 255 /// This function is used to (re-)set the authentication token of the user.
271 /// 256 ///
272 /// The PAM library calls this function twice in succession. The first time with 257 /// The PAM library calls this function twice in succession. The first time with
273 /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with 258 /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with
274 /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is 259 /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is
275 /// (possibly) changed. 260 /// (possibly) changed.
276 fn sm_chauthtok(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { 261 fn sm_chauthtok(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
277 PamResultCode::PAM_IGNORE 262 Err(ErrorCode::Ignore)
278 } 263 }
279 264
280 /// This function is called to terminate a session. 265 /// This function is called to terminate a session.
281 fn sm_close_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { 266 fn sm_close_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
282 PamResultCode::PAM_IGNORE 267 Err(ErrorCode::Ignore)
283 } 268 }
284 269
285 /// This function is called to commence a session. 270 /// This function is called to commence a session.
286 fn sm_open_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { 271 fn sm_open_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
287 PamResultCode::PAM_IGNORE 272 Err(ErrorCode::Ignore)
288 } 273 }
289 274
290 /// This function performs the task of altering the credentials of the user with respect to the 275 /// This function performs the task of altering the credentials of the user with respect to the
291 /// corresponding authorization scheme. Generally, an authentication module may have access to more 276 /// corresponding authorization scheme. Generally, an authentication module may have access to more
292 /// information about a user than their authentication token. This function is used to make such 277 /// information about a user than their authentication token. This function is used to make such
293 /// information available to the application. It should only be called after the user has been 278 /// information available to the application. It should only be called after the user has been
294 /// authenticated but before a session has been established. 279 /// authenticated but before a session has been established.
295 fn sm_setcred(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { 280 fn sm_setcred(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
296 PamResultCode::PAM_IGNORE 281 Err(ErrorCode::Ignore)
297 } 282 }
298 } 283 }