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