comparison src/libpam/handle.rs @ 143:ebb71a412b58

Turn everything into OsString and Just Walk Out! for strings with nul. To reduce the hazard surface of the API, this replaces most uses of &str with &OsStr (and likewise with String/OsString). Also, I've decided that instead of dealing with callers putting `\0` in their parameters, I'm going to follow the example of std::env and Just Walk Out! (i.e., panic!()). This makes things a lot less annoying for both me and (hopefully) users.
author Paul Fisher <paul@pfish.zone>
date Sat, 05 Jul 2025 22:12:46 -0400
parents a508a69c068a
children 56b559b7ecea
comparison
equal deleted inserted replaced
142:5c1e315c18ff 143:ebb71a412b58
11 PamHandleModule, 11 PamHandleModule,
12 }; 12 };
13 use libpam_sys_helpers::constants; 13 use libpam_sys_helpers::constants;
14 use num_enum::{IntoPrimitive, TryFromPrimitive}; 14 use num_enum::{IntoPrimitive, TryFromPrimitive};
15 use std::cell::Cell; 15 use std::cell::Cell;
16 use std::ffi::{c_char, c_int, CString}; 16 use std::ffi::{c_char, c_int, CString, OsStr, OsString};
17 use std::mem::ManuallyDrop; 17 use std::mem::ManuallyDrop;
18 use std::os::unix::ffi::OsStrExt;
18 use std::ptr; 19 use std::ptr;
19 use std::ptr::NonNull; 20 use std::ptr::NonNull;
20 21
21 /// An owned PAM handle. 22 /// An owned PAM handle.
22 pub struct OwnedLibPamHandle<C: Conversation> { 23 pub struct OwnedLibPamHandle<C: Conversation> {
34 conversation: Box<OwnedConversation<C>>, 35 conversation: Box<OwnedConversation<C>>,
35 } 36 }
36 37
37 #[derive(Debug, PartialEq)] 38 #[derive(Debug, PartialEq)]
38 pub struct HandleBuilder { 39 pub struct HandleBuilder {
39 service_name: String, 40 service_name: OsString,
40 username: Option<String>, 41 username: Option<OsString>,
41 } 42 }
42 43
43 impl HandleBuilder { 44 impl HandleBuilder {
44 /// Updates the service name. 45 /// Updates the service name.
45 pub fn service_name(mut self, service_name: String) -> Self { 46 pub fn service_name(mut self, service_name: OsString) -> Self {
46 self.service_name = service_name; 47 self.service_name = service_name;
47 self 48 self
48 } 49 }
49 /// Sets the username. Setting this will avoid the need for an extra 50 /// Sets the username. Setting this will avoid the need for an extra
50 /// round trip through the conversation and may otherwise improve 51 /// round trip through the conversation and may otherwise improve
51 /// the login experience. 52 /// the login experience.
52 pub fn username(mut self, username: String) -> Self { 53 pub fn username(mut self, username: OsString) -> Self {
53 self.username = Some(username); 54 self.username = Some(username);
54 self 55 self
55 } 56 }
56 /// Builds a PAM handle and starts the transaction. 57 /// Builds a PAM handle and starts the transaction.
57 pub fn build(self, conv: impl Conversation) -> Result<OwnedLibPamHandle<impl Conversation>> { 58 pub fn build(self, conv: impl Conversation) -> Result<OwnedLibPamHandle<impl Conversation>> {
69 /// # References 70 /// # References
70 #[doc = linklist!(pam_start: adg, _std)] 71 #[doc = linklist!(pam_start: adg, _std)]
71 /// 72 ///
72 #[doc = stdlinks!(3 pam_start)] 73 #[doc = stdlinks!(3 pam_start)]
73 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_start")] 74 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_start")]
74 pub fn build_with_service(service_name: String) -> HandleBuilder { 75 pub fn build_with_service(service_name: OsString) -> HandleBuilder {
75 HandleBuilder { 76 HandleBuilder {
76 service_name, 77 service_name,
77 username: None, 78 username: None,
78 } 79 }
79 } 80 }
80 81
81 fn start(service_name: String, username: Option<String>, conversation: C) -> Result<Self> { 82 fn start(service_name: OsString, username: Option<OsString>, conversation: C) -> Result<Self> {
82 let conv = Box::new(OwnedConversation::new(conversation)); 83 let conv = Box::new(OwnedConversation::new(conversation));
83 let service_cstr = CString::new(service_name).map_err(|_| ErrorCode::ConversationError)?; 84 let service_cstr = CString::new(service_name.as_bytes()).expect("null is forbidden");
84 let username_cstr = memory::prompt_ptr(memory::option_cstr(username.as_deref())?.as_ref()); 85 let username_cstr = memory::option_cstr_os(username.as_deref());
86 let username_cstr = memory::prompt_ptr(username_cstr.as_deref());
85 87
86 let mut handle: *mut libpam_sys::pam_handle = ptr::null_mut(); 88 let mut handle: *mut libpam_sys::pam_handle = ptr::null_mut();
87 // SAFETY: We've set everything up properly to call `pam_start`. 89 // SAFETY: We've set everything up properly to call `pam_start`.
88 // The returned value will be a valid pointer provided the result is OK. 90 // The returned value will be a valid pointer provided the result is OK.
89 let result = unsafe { 91 let result = unsafe {
189 self.handle.$meth($($param),*) 191 self.handle.$meth($($param),*)
190 } 192 }
191 }; 193 };
192 // Then have item getters / setters 194 // Then have item getters / setters
193 (get = $get:ident$(, set = $set:ident)?) => { 195 (get = $get:ident$(, set = $set:ident)?) => {
194 delegate!(fn $get(&self) -> Result<Option<String>>); 196 delegate!(fn $get(&self) -> Result<Option<OsString>>);
195 $(delegate!(set = $set);)? 197 $(delegate!(set = $set);)?
196 }; 198 };
197 (set = $set:ident) => { 199 (set = $set:ident) => {
198 delegate!(fn $set(&mut self, value: Option<&str>) -> Result<()>); 200 delegate!(fn $set(&mut self, value: Option<&OsStr>) -> Result<()>);
199 }; 201 };
200 } 202 }
201 203
202 fn split<T>(result: &Result<T>) -> Result<()> { 204 fn split<T>(result: &Result<T>) -> Result<()> {
203 result.as_ref().map(drop).map_err(|&e| e) 205 result.as_ref().map(drop).map_err(|&e| e)
205 207
206 impl<C: Conversation> PamShared for OwnedLibPamHandle<C> { 208 impl<C: Conversation> PamShared for OwnedLibPamHandle<C> {
207 delegate!(fn log(&self, level: Level, location: Location<'_>, entry: &str) -> ()); 209 delegate!(fn log(&self, level: Level, location: Location<'_>, entry: &str) -> ());
208 delegate!(fn environ(&self) -> impl EnvironMap); 210 delegate!(fn environ(&self) -> impl EnvironMap);
209 delegate!(fn environ_mut(&mut self) -> impl EnvironMapMut); 211 delegate!(fn environ_mut(&mut self) -> impl EnvironMapMut);
210 delegate!(fn username(&mut self, prompt: Option<&str>) -> Result<String>); 212 delegate!(fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString>);
211 delegate!(get = user_item, set = set_user_item); 213 delegate!(get = user_item, set = set_user_item);
212 delegate!(get = service, set = set_service); 214 delegate!(get = service, set = set_service);
213 delegate!(get = user_prompt, set = set_user_prompt); 215 delegate!(get = user_prompt, set = set_user_prompt);
214 delegate!(get = tty_name, set = set_tty_name); 216 delegate!(get = tty_name, set = set_tty_name);
215 delegate!(get = remote_user, set = set_remote_user); 217 delegate!(get = remote_user, set = set_remote_user);
219 } 221 }
220 222
221 /// Macro to implement getting/setting a CStr-based item. 223 /// Macro to implement getting/setting a CStr-based item.
222 macro_rules! cstr_item { 224 macro_rules! cstr_item {
223 (get = $getter:ident, item = $item_type:path) => { 225 (get = $getter:ident, item = $item_type:path) => {
224 fn $getter(&self) -> Result<Option<String>> { 226 fn $getter(&self) -> Result<Option<OsString>> {
225 unsafe { self.get_cstr_item($item_type) } 227 unsafe { self.get_cstr_item($item_type) }
226 } 228 }
227 }; 229 };
228 (set = $setter:ident, item = $item_type:path) => { 230 (set = $setter:ident, item = $item_type:path) => {
229 fn $setter(&mut self, value: Option<&str>) -> Result<()> { 231 fn $setter(&mut self, value: Option<&OsStr>) -> Result<()> {
230 unsafe { self.set_cstr_item($item_type, value) } 232 unsafe { self.set_cstr_item($item_type, value) }
231 } 233 }
232 }; 234 };
233 } 235 }
234 236
326 fn log(&self, level: Level, loc: Location<'_>, entry: &str) { 328 fn log(&self, level: Level, loc: Location<'_>, entry: &str) {
327 let entry = match CString::new(entry).or_else(|_| CString::new(dbg!(entry))) { 329 let entry = match CString::new(entry).or_else(|_| CString::new(dbg!(entry))) {
328 Ok(cstr) => cstr, 330 Ok(cstr) => cstr,
329 _ => return, 331 _ => return,
330 }; 332 };
331 #[cfg(pam_impl = "linux-pam")] 333 #[cfg(pam_impl = "LinuxPam")]
332 { 334 {
333 _ = loc; 335 _ = loc;
334 // SAFETY: We're calling this function with a known value. 336 // SAFETY: We're calling this function with a known value.
335 unsafe { 337 unsafe {
336 libpam_sys::pam_syslog(self, level as c_int, "%s\0".as_ptr().cast(), entry.as_ptr()) 338 libpam_sys::pam_syslog(self, level as c_int, "%s\0".as_ptr().cast(), entry.as_ptr())
337 } 339 }
338 } 340 }
339 #[cfg(pam_impl = "openpam")] 341 #[cfg(pam_impl = "OpenPam")]
340 { 342 {
341 let func = CString::new(loc.function).unwrap_or(CString::default()); 343 let func = CString::new(loc.function).unwrap_or(CString::default());
342 // SAFETY: We're calling this function with a known value. 344 // SAFETY: We're calling this function with a known value.
343 unsafe { 345 unsafe {
344 libpam_sys::_openpam_log( 346 libpam_sys::_openpam_log(
351 } 353 }
352 } 354 }
353 355
354 fn log(&self, _level: Level, _loc: Location<'_>, _entry: &str) {} 356 fn log(&self, _level: Level, _loc: Location<'_>, _entry: &str) {}
355 357
356 fn username(&mut self, prompt: Option<&str>) -> Result<String> { 358 fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString> {
357 let prompt = memory::option_cstr(prompt)?; 359 let prompt = memory::option_cstr_os(prompt);
358 let mut output: *const c_char = ptr::null(); 360 let mut output: *const c_char = ptr::null();
359 let ret = unsafe { 361 let ret = unsafe {
360 libpam_sys::pam_get_user( 362 libpam_sys::pam_get_user(
361 self.raw_mut(), 363 self.raw_mut(),
362 &mut output, 364 &mut output,
363 memory::prompt_ptr(prompt.as_ref()), 365 memory::prompt_ptr(prompt.as_deref()),
364 ) 366 )
365 }; 367 };
366 ErrorCode::result_from(ret)?; 368 ErrorCode::result_from(ret)?;
367 unsafe { memory::copy_pam_string(output) } 369 Ok(unsafe { memory::copy_pam_string(output).ok_or(ErrorCode::ConversationError)? })
368 .transpose()
369 .unwrap_or(Err(ErrorCode::ConversationError))
370 } 370 }
371 371
372 fn environ(&self) -> impl EnvironMap { 372 fn environ(&self) -> impl EnvironMap {
373 LibPamEnviron::new(self) 373 LibPamEnviron::new(self)
374 } 374 }
405 } 405 }
406 } 406 }
407 } 407 }
408 408
409 impl PamHandleModule for RawPamHandle { 409 impl PamHandleModule for RawPamHandle {
410 fn authtok(&mut self, prompt: Option<&str>) -> Result<String> { 410 fn authtok(&mut self, prompt: Option<&OsStr>) -> Result<OsString> {
411 self.get_authtok(prompt, ItemType::AuthTok) 411 self.get_authtok(prompt, ItemType::AuthTok)
412 } 412 }
413 413
414 fn old_authtok(&mut self, prompt: Option<&str>) -> Result<String> { 414 fn old_authtok(&mut self, prompt: Option<&OsStr>) -> Result<OsString> {
415 self.get_authtok(prompt, ItemType::OldAuthTok) 415 self.get_authtok(prompt, ItemType::OldAuthTok)
416 } 416 }
417 417
418 cstr_item!(get = authtok_item, item = ItemType::AuthTok); 418 cstr_item!(get = authtok_item, item = ItemType::AuthTok);
419 cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok); 419 cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok);
430 } 430 }
431 431
432 // Implementations of internal functions. 432 // Implementations of internal functions.
433 impl RawPamHandle { 433 impl RawPamHandle {
434 #[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))] 434 #[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))]
435 fn get_authtok(&mut self, prompt: Option<&str>, item_type: ItemType) -> Result<String> { 435 fn get_authtok(&mut self, prompt: Option<&OsStr>, item_type: ItemType) -> Result<OsString> {
436 let prompt = memory::option_cstr(prompt)?; 436 let prompt = memory::option_cstr_os(prompt);
437 let mut output: *const c_char = ptr::null_mut(); 437 let mut output: *const c_char = ptr::null_mut();
438 // SAFETY: We're calling this with known-good values. 438 // SAFETY: We're calling this with known-good values.
439 let res = unsafe { 439 let res = unsafe {
440 libpam_sys::pam_get_authtok( 440 libpam_sys::pam_get_authtok(
441 self.raw_mut(), 441 self.raw_mut(),
442 item_type.into(), 442 item_type.into(),
443 &mut output, 443 &mut output,
444 memory::prompt_ptr(prompt.as_ref()), 444 memory::prompt_ptr(prompt.as_deref()),
445 ) 445 )
446 }; 446 };
447 ErrorCode::result_from(res)?; 447 ErrorCode::result_from(res)?;
448 // SAFETY: We got this string from PAM. 448 // SAFETY: We got this string from PAM.
449 unsafe { memory::copy_pam_string(output) } 449 unsafe { memory::copy_pam_string(output) }.ok_or(ErrorCode::ConversationError)
450 .transpose()
451 .unwrap_or(Err(ErrorCode::ConversationError))
452 } 450 }
453 451
454 #[cfg(not(any(pam_impl = "LinuxPam", pam_impl = "OpenPam")))] 452 #[cfg(not(any(pam_impl = "LinuxPam", pam_impl = "OpenPam")))]
455 fn get_authtok(&mut self, prompt: Option<&str>, item_type: ItemType) -> Result<String> { 453 fn get_authtok(&mut self, prompt: Option<&str>, item_type: ItemType) -> Result<String> {
456 Err(ErrorCode::ConversationError) 454 Err(ErrorCode::ConversationError)
459 /// Gets a C string item. 457 /// Gets a C string item.
460 /// 458 ///
461 /// # Safety 459 /// # Safety
462 /// 460 ///
463 /// You better be requesting an item which is a C string. 461 /// You better be requesting an item which is a C string.
464 unsafe fn get_cstr_item(&self, item_type: ItemType) -> Result<Option<String>> { 462 unsafe fn get_cstr_item(&self, item_type: ItemType) -> Result<Option<OsString>> {
465 let mut output = ptr::null(); 463 let mut output = ptr::null();
466 let ret = 464 let ret =
467 unsafe { libpam_sys::pam_get_item(self.raw_ref(), item_type as c_int, &mut output) }; 465 unsafe { libpam_sys::pam_get_item(self.raw_ref(), item_type as c_int, &mut output) };
468 ErrorCode::result_from(ret)?; 466 ErrorCode::result_from(ret)?;
469 memory::copy_pam_string(output.cast()) 467 Ok(memory::copy_pam_string(output.cast()))
470 } 468 }
471 469
472 /// Sets a C string item. 470 /// Sets a C string item.
473 /// 471 ///
474 /// # Safety 472 /// # Safety
475 /// 473 ///
476 /// You better be setting an item which is a C string. 474 /// You better be setting an item which is a C string.
477 unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> { 475 unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&OsStr>) -> Result<()> {
478 let data_str = memory::option_cstr(data)?; 476 let data_str = memory::option_cstr_os(data);
479 let ret = unsafe { 477 let ret = unsafe {
480 libpam_sys::pam_set_item( 478 libpam_sys::pam_set_item(
481 self.raw_mut(), 479 self.raw_mut(),
482 item_type as c_int, 480 item_type as c_int,
483 memory::prompt_ptr(data_str.as_ref()).cast(), 481 memory::prompt_ptr(data_str.as_deref()).cast(),
484 ) 482 )
485 }; 483 };
486 ErrorCode::result_from(ret) 484 ErrorCode::result_from(ret)
487 } 485 }
488 486