comparison src/module.rs @ 51:9d1160b02d2c

Safety and doc fixes: - Don't panic when given a string with a null character; instead return `PAM_CONV_ERR`. - Improve pattern matching and use ?s where appropriate. - Format etc.
author Paul Fisher <paul@pfish.zone>
date Sat, 03 May 2025 18:41:25 -0400
parents a921b72743e4
children
comparison
equal deleted inserted replaced
50:171fb1171053 51:9d1160b02d2c
1 //! Functions for use in pam modules. 1 //! Functions for use in pam modules.
2 2
3 use crate::constants::{PamFlag, PamResultCode};
4 use crate::items::{Item, ItemType};
3 use libc::c_char; 5 use libc::c_char;
4 use std::ffi::{CStr, CString}; 6 use std::ffi::{CStr, CString};
5
6 use crate::constants::{PamFlag, PamResultCode};
7 use crate::items::ItemType;
8 7
9 /// Opaque type, used as a pointer when making pam API calls. 8 /// Opaque type, used as a pointer when making pam API calls.
10 /// 9 ///
11 /// A module is invoked via an external function such as `pam_sm_authenticate`. 10 /// A module is invoked via an external function such as `pam_sm_authenticate`.
12 /// Such a call provides a pam handle pointer. The same pointer should be given 11 /// Such a call provides a pam handle pointer. The same pointer should be given
83 /// 82 ///
84 /// # Safety 83 /// # Safety
85 /// 84 ///
86 /// The data stored under the provided key must be of type `T` otherwise the 85 /// The data stored under the provided key must be of type `T` otherwise the
87 /// behaviour of this function is undefined. 86 /// behaviour of this function is undefined.
88 pub unsafe fn get_data<T>(&self, key: &str) -> PamResult<&T> { 87 ///
89 let c_key = CString::new(key).unwrap(); 88 /// The data, if present, is owned by the current PAM conversation.
89 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)?;
90 let mut ptr: *const libc::c_void = std::ptr::null(); 91 let mut ptr: *const libc::c_void = std::ptr::null();
91 let res = pam_get_data(self, c_key.as_ptr(), &mut ptr); 92 to_result(pam_get_data(self, c_key.as_ptr(), &mut ptr))?;
92 if PamResultCode::PAM_SUCCESS == res && !ptr.is_null() { 93 match ptr.is_null() {
93 let typed_ptr = ptr.cast::<T>(); 94 true => Ok(None),
94 let data: &T = &*typed_ptr; 95 false => {
95 Ok(data) 96 let typed_ptr = ptr.cast::<T>();
96 } else { 97 Ok(Some(&*typed_ptr))
97 Err(res) 98 }
98 } 99 }
99 } 100 }
100 101
101 /// Stores a value that can be retrieved later with `get_data`. The value lives 102 /// Stores a value that can be retrieved later with `get_data`.
102 /// as long as the current pam cycle. 103 /// The conversation takes ownership of the data.
103 /// 104 ///
104 /// See the [`pam_set_data` manual page]( 105 /// See the [`pam_set_data` manual page](
105 /// https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html). 106 /// https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html).
106 /// 107 ///
107 /// # Errors 108 /// # Errors
108 /// 109 ///
109 /// Returns an error if the underlying PAM function call fails. 110 /// Returns an error if the underlying PAM function call fails.
110 pub fn set_data<T>(&self, key: &str, data: Box<T>) -> PamResult<()> { 111 pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> PamResult<()> {
111 let c_key = CString::new(key).unwrap(); 112 let c_key = CString::new(key).map_err(|_| PamResultCode::PAM_CONV_ERR)?;
112 let res = unsafe { 113 let res = unsafe {
113 pam_set_data( 114 pam_set_data(
114 self, 115 self,
115 c_key.as_ptr(), 116 c_key.as_ptr(),
116 Box::into_raw(data).cast::<libc::c_void>(), 117 Box::into_raw(data).cast::<libc::c_void>(),
118 ) 119 )
119 }; 120 };
120 to_result(res) 121 to_result(res)
121 } 122 }
122 123
123 /// Retrieves a value that has been set, possibly by the pam client. This is 124 /// Retrieves a value that has been set, possibly by the pam client.
124 /// particularly useful for getting a `PamConv` reference. 125 /// This is particularly useful for getting a `PamConv` reference.
126 ///
127 /// These items are *references to PAM memory*
128 /// which are *owned by the conversation*.
125 /// 129 ///
126 /// See the [`pam_get_item` manual page]( 130 /// See the [`pam_get_item` manual page](
127 /// https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html). 131 /// https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html).
128 /// 132 ///
129 /// # Errors 133 /// # Errors
130 /// 134 ///
131 /// Returns an error if the underlying PAM function call fails. 135 /// Returns an error if the underlying PAM function call fails.
132 pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> { 136 pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> {
133 let mut ptr: *const libc::c_void = std::ptr::null(); 137 let mut ptr: *const libc::c_void = std::ptr::null();
134 let (res, item) = unsafe { 138 let out = unsafe {
135 let r = pam_get_item(self, T::type_id(), &mut ptr); 139 let r = pam_get_item(self, T::type_id(), &mut ptr);
140 to_result(r)?;
136 let typed_ptr = ptr.cast::<T::Raw>(); 141 let typed_ptr = ptr.cast::<T::Raw>();
137 let t = if typed_ptr.is_null() { 142 match typed_ptr.is_null() {
138 None 143 true => None,
139 } else { 144 false => Some(T::from_raw(typed_ptr)),
140 Some(T::from_raw(typed_ptr)) 145 }
141 };
142 (r, t)
143 }; 146 };
144 match res { 147 Ok(out)
145 PamResultCode::PAM_SUCCESS => Ok(item), 148 }
146 other => Err(other), 149
147 } 150 /// Sets an item in the pam context. It can be retrieved using `get_item`.
148 }
149
150 /// Sets a value in the pam context. The value can be retrieved using
151 /// `get_item`.
152 ///
153 /// Note that all items are strings, except `PAM_CONV` and `PAM_FAIL_DELAY`.
154 /// 151 ///
155 /// See the [`pam_set_item` manual page]( 152 /// See the [`pam_set_item` manual page](
156 /// https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html). 153 /// https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html).
157 /// 154 ///
158 /// # Errors 155 /// # Errors
159 /// 156 ///
160 /// Returns an error if the underlying PAM function call fails. 157 /// Returns an error if the underlying PAM function call fails.
161 /// 158 pub fn set_item<T: Item>(&mut self, item: T) -> PamResult<()> {
162 /// # Panics
163 ///
164 /// Panics if the provided item key contains a nul byte.
165 pub fn set_item_str<T: crate::items::Item>(&mut self, item: T) -> PamResult<()> {
166 let res = 159 let res =
167 unsafe { pam_set_item(self, T::type_id(), item.into_raw().cast::<libc::c_void>()) }; 160 unsafe { pam_set_item(self, T::type_id(), item.into_raw().cast::<libc::c_void>()) };
168 to_result(res) 161 to_result(res)
169 } 162 }
170 163
176 /// https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html). 169 /// https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html).
177 /// 170 ///
178 /// # Errors 171 /// # Errors
179 /// 172 ///
180 /// Returns an error if the underlying PAM function call fails. 173 /// Returns an error if the underlying PAM function call fails.
181 ///
182 /// # Panics
183 ///
184 /// Panics if the provided prompt string contains a nul byte.
185 pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> { 174 pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> {
186 let prompt_string; 175 let prompt = option_cstr(prompt)?;
187 let c_prompt = match prompt {
188 Some(p) => {
189 prompt_string = CString::new(p).unwrap();
190 prompt_string.as_ptr()
191 }
192 None => std::ptr::null(),
193 };
194 let output: *mut c_char = std::ptr::null_mut(); 176 let output: *mut c_char = std::ptr::null_mut();
195 let res = unsafe { pam_get_user(self, &output, c_prompt) }; 177 let res = unsafe { pam_get_user(self, &output, prompt_ptr(prompt.as_ref())) };
196 match res { 178 match res {
197 PamResultCode::PAM_SUCCESS => copy_pam_string(output), 179 PamResultCode::PAM_SUCCESS => copy_pam_string(output),
198 otherwise => Err(otherwise), 180 otherwise => Err(otherwise),
199 } 181 }
200 } 182 }
207 /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html). 189 /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html).
208 /// 190 ///
209 /// # Errors 191 /// # Errors
210 /// 192 ///
211 /// Returns an error if the underlying PAM function call fails. 193 /// Returns an error if the underlying PAM function call fails.
212 ///
213 /// # Panics
214 ///
215 /// Panics if the provided prompt string contains a nul byte.
216 pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<String> { 194 pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<String> {
217 let prompt_string; 195 let prompt = option_cstr(prompt)?;
218 let c_prompt = match prompt { 196 let output: *mut c_char = std::ptr::null_mut();
219 Some(p) => { 197 let res = unsafe {
220 prompt_string = CString::new(p).unwrap(); 198 pam_get_authtok(
221 prompt_string.as_ptr() 199 self,
222 } 200 ItemType::AuthTok,
223 None => std::ptr::null(), 201 &output,
202 prompt_ptr(prompt.as_ref()),
203 )
224 }; 204 };
225 let output: *mut c_char = std::ptr::null_mut(); 205 to_result(res)?;
226 let res = unsafe { pam_get_authtok(self, ItemType::AuthTok, &output, c_prompt) }; 206 copy_pam_string(output)
227 match res { 207 }
228 PamResultCode::PAM_SUCCESS => copy_pam_string(output), 208 }
229 otherwise => Err(otherwise), 209
230 } 210 /// Safely converts a `&str` option to a `CString` option.
211 fn option_cstr(prompt: Option<&str>) -> PamResult<Option<CString>> {
212 prompt
213 .map(CString::new)
214 .transpose()
215 .map_err(|_| PamResultCode::PAM_CONV_ERR)
216 }
217
218 /// The pointer to the prompt CString, or null if absent.
219 fn prompt_ptr(prompt: Option<&CString>) -> *const c_char {
220 match prompt {
221 Some(c_str) => c_str.as_ptr(),
222 None => std::ptr::null(),
231 } 223 }
232 } 224 }
233 225
234 /// Creates an owned copy of a string that is returned from a 226 /// Creates an owned copy of a string that is returned from a
235 /// <code>pam_get_<var>whatever</var></code> function. 227 /// <code>pam_get_<var>whatever</var></code> function.
236 fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> { 228 fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> {
237 // We really shouldn't get a null pointer back here, but if we do, return nothing. 229 // We really shouldn't get a null pointer back here, but if we do, return nothing.
238 if result_ptr.is_null() { 230 if result_ptr.is_null() {
239 return Ok(String::from("")); 231 return Ok(String::new());
240 } 232 }
241 let bytes = unsafe { CStr::from_ptr(result_ptr).to_bytes() }; 233 let bytes = unsafe { CStr::from_ptr(result_ptr) };
242 String::from_utf8(bytes.to_vec()).map_err(|_| PamResultCode::PAM_CONV_ERR) 234 Ok(bytes
235 .to_str()
236 .map_err(|_| PamResultCode::PAM_CONV_ERR)?
237 .into())
243 } 238 }
244 239
245 /// Convenience to transform a `PamResultCode` into a unit `PamResult`. 240 /// Convenience to transform a `PamResultCode` into a unit `PamResult`.
246 fn to_result(result: PamResultCode) -> PamResult<()> { 241 fn to_result(result: PamResultCode) -> PamResult<()> {
247 match result { 242 match result {