comparison src/libpam/handle.rs @ 159:634cd5f2ac8b

Separate logging into its own trait apart from the rest of PAM.
author Paul Fisher <paul@pfish.zone>
date Sat, 12 Jul 2025 18:16:18 -0400
parents 0099f2f79f86
children a75a66cb4181
comparison
equal deleted inserted replaced
158:d5b7b28d754e 159:634cd5f2ac8b
6 use crate::handle::PamShared; 6 use crate::handle::PamShared;
7 use crate::items::{Items, ItemsMut}; 7 use crate::items::{Items, ItemsMut};
8 use crate::libpam::environ::{LibPamEnviron, LibPamEnvironMut}; 8 use crate::libpam::environ::{LibPamEnviron, LibPamEnvironMut};
9 use crate::libpam::items::{LibPamItems, LibPamItemsMut}; 9 use crate::libpam::items::{LibPamItems, LibPamItemsMut};
10 use crate::libpam::{items, memory}; 10 use crate::libpam::{items, memory};
11 use crate::logging::{Level, Location}; 11 use crate::logging::{Level, Location, Logger};
12 use crate::{Conversation, EnvironMap, Flags, ModuleClient, Transaction}; 12 use crate::{Conversation, EnvironMap, Flags, ModuleClient, Transaction};
13 use libpam_sys_consts::constants; 13 use libpam_sys_consts::constants;
14 use num_enum::{IntoPrimitive, TryFromPrimitive}; 14 use num_enum::{IntoPrimitive, TryFromPrimitive};
15 use std::any::TypeId; 15 use std::any::TypeId;
16 use std::cell::Cell; 16 use std::cell::Cell;
41 service_name: OsString, 41 service_name: OsString,
42 username: Option<OsString>, 42 username: Option<OsString>,
43 } 43 }
44 44
45 impl TransactionBuilder { 45 impl TransactionBuilder {
46 /// Creates a builder to start a PAM transaction for the given service.
47 ///
48 /// The service name is what controls the steps and checks PAM goes through
49 /// when authenticating a user. This corresponds to the configuration file
50 /// usually at <code>/etc/pam.d/<var>service_name</var></code>.
51 ///
52 /// # References
53 #[doc = linklist!(pam_start: adg, _std)]
54 ///
55 #[doc = stdlinks!(3 pam_start)]
56 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_start")]
57 pub fn new_with_service(service_name: impl AsRef<OsStr>) -> Self {
58 Self {
59 service_name: service_name.as_ref().into(),
60 username: None,
61 }
62 }
63
46 /// Updates the service name. 64 /// Updates the service name.
47 pub fn service_name(mut self, service_name: OsString) -> Self { 65 pub fn service_name(mut self, service_name: impl AsRef<OsStr>) -> Self {
48 self.service_name = service_name; 66 self.service_name = service_name.as_ref().into();
49 self 67 self
50 } 68 }
69
51 /// Sets the username. Setting this will avoid the need for an extra 70 /// Sets the username. Setting this will avoid the need for an extra
52 /// round trip through the conversation and may otherwise improve 71 /// round trip through the conversation and may otherwise improve
53 /// the login experience. 72 /// the login experience.
54 pub fn username(mut self, username: OsString) -> Self { 73 pub fn username(mut self, username: impl AsRef<OsStr>) -> Self {
55 self.username = Some(username); 74 self.username = Some(username.as_ref().into());
56 self 75 self
57 } 76 }
58 /// Builds a PAM handle and starts the transaction. 77
78 /// Builds the PAM handle and starts the transaction.
59 pub fn build(self, conv: impl Conversation) -> Result<LibPamTransaction<impl Conversation>> { 79 pub fn build(self, conv: impl Conversation) -> Result<LibPamTransaction<impl Conversation>> {
60 LibPamTransaction::start(self.service_name, self.username, conv) 80 LibPamTransaction::start(self.service_name, self.username, conv)
61 } 81 }
62 } 82 }
63 83
64 impl<C: Conversation> LibPamTransaction<C> { 84 impl<C: Conversation> LibPamTransaction<C> {
65 /// Creates a builder to start a PAM transaction for the given service.
66 ///
67 /// The service name is what controls the steps and checks PAM goes through
68 /// when authenticating a user. This corresponds to the configuration file
69 /// named <code>/etc/pam.d/<var>service_name</var></code>.
70 ///
71 /// # References
72 #[doc = linklist!(pam_start: adg, _std)]
73 ///
74 #[doc = stdlinks!(3 pam_start)]
75 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_start")]
76 pub fn build_with_service(service_name: OsString) -> TransactionBuilder {
77 TransactionBuilder {
78 service_name,
79 username: None,
80 }
81 }
82
83 fn start(service_name: OsString, username: Option<OsString>, conversation: C) -> Result<Self> { 85 fn start(service_name: OsString, username: Option<OsString>, conversation: C) -> Result<Self> {
84 let conv = Box::new(OwnedConversation::new(conversation)); 86 let conv = Box::new(OwnedConversation::new(conversation));
85 let service_cstr = CString::new(service_name.as_bytes()).expect("null is forbidden"); 87 let service_cstr = CString::new(service_name.as_bytes()).expect("null is forbidden");
86 let username_cstr = memory::option_cstr_os(username.as_deref()); 88 let username_cstr = memory::option_cstr_os(username.as_deref());
87 let username_cstr = memory::prompt_ptr(username_cstr.as_deref()); 89 let username_cstr = memory::prompt_ptr(username_cstr.as_deref());
214 216
215 fn split<T>(result: &Result<T>) -> Result<()> { 217 fn split<T>(result: &Result<T>) -> Result<()> {
216 result.as_ref().map(drop).map_err(|&e| e) 218 result.as_ref().map(drop).map_err(|&e| e)
217 } 219 }
218 220
221 impl<C: Conversation> Logger for LibPamTransaction<C> {
222 delegate!(fn log(&self, level: Level, location: Location<'_>, entry: fmt::Arguments) -> ());
223 }
224
219 impl<C: Conversation> Transaction for LibPamTransaction<C> { 225 impl<C: Conversation> Transaction for LibPamTransaction<C> {
220 delegate!(fn authenticate(&mut self, flags: Flags) -> Result<()>); 226 delegate!(fn authenticate(&mut self, flags: Flags) -> Result<()>);
221 delegate!(fn account_management(&mut self, flags: Flags) -> Result<()>); 227 delegate!(fn account_management(&mut self, flags: Flags) -> Result<()>);
222 delegate!(fn change_authtok(&mut self, flags: Flags) -> Result<()>); 228 delegate!(fn change_authtok(&mut self, flags: Flags) -> Result<()>);
223 } 229 }
224 230
225 impl<C: Conversation> PamShared for LibPamTransaction<C> { 231 impl<C: Conversation> PamShared for LibPamTransaction<C> {
226 delegate!(fn log(&self, level: Level, location: Location<'_>, entry: fmt::Arguments) -> ());
227 delegate!(fn environ(&self) -> impl EnvironMap); 232 delegate!(fn environ(&self) -> impl EnvironMap);
228 delegate!(fn environ_mut(&mut self) -> impl EnvironMapMut); 233 delegate!(fn environ_mut(&mut self) -> impl EnvironMapMut);
229 delegate!(fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString>); 234 delegate!(fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString>);
230 delegate!(fn items(&self) -> impl Items); 235 delegate!(fn items(&self) -> impl Items);
231 delegate!(fn items_mut(&mut self) -> impl ItemsMut); 236 delegate!(fn items_mut(&mut self) -> impl ItemsMut);
319 fn drop(&mut self) { 324 fn drop(&mut self) {
320 unsafe { libpam_sys::pam_end(self.0.as_mut(), 0) }; 325 unsafe { libpam_sys::pam_end(self.0.as_mut(), 0) };
321 } 326 }
322 } 327 }
323 328
324 impl PamShared for LibPamHandle { 329 impl Logger for LibPamHandle {
325 fn log(&self, level: Level, loc: Location<'_>, entry: fmt::Arguments) { 330 fn log(&self, level: Level, loc: Location<'_>, entry: fmt::Arguments) {
326 let entry = match CString::new(entry.to_string()).ok() { 331 let entry = match CString::new(entry.to_string()).ok() {
327 Some(e) => e, 332 Some(e) => e,
328 None => return, 333 None => return,
329 }; 334 };
330 #[cfg(pam_impl = "LinuxPam")] 335 #[cfg(any(pam_impl = "LinuxPam", pam_impl = "Sun"))]
331 { 336 {
332 let level = match level { 337 let level = match level {
333 Level::Error => libc::LOG_ERR, 338 Level::Error => libc::LOG_ERR,
334 Level::Warn => libc::LOG_WARNING, 339 Level::Warn => libc::LOG_WARNING,
335 Level::Info => libc::LOG_INFO, 340 Level::Info => libc::LOG_INFO,
336 Level::Debug => libc::LOG_DEBUG, 341 Level::Debug => libc::LOG_DEBUG,
337 }; 342 };
338 _ = loc; 343 _ = loc;
339 // SAFETY: We're calling this function with a known value. 344 // SAFETY: We're calling this function with a known value.
345 #[cfg(pam_impl = "LinuxPam")]
340 unsafe { 346 unsafe {
341 libpam_sys::pam_syslog( 347 libpam_sys::pam_syslog(
342 self.raw_ref(), 348 self.raw_ref(),
343 level, 349 level,
344 b"%s\0".as_ptr().cast(), 350 b"%s\0".as_ptr().cast(),
345 entry.as_ptr(), 351 entry.as_ptr(),
346 ) 352 )
353 }
354 #[cfg(pam_impl = "Sun")]
355 unsafe {
356 libpam_sys::__pam_log(level, b"%s\0".as_ptr().cast(), entry.as_ptr())
347 } 357 }
348 } 358 }
349 #[cfg(pam_impl = "OpenPam")] 359 #[cfg(pam_impl = "OpenPam")]
350 { 360 {
351 let func = CString::new(loc.function).unwrap_or(CString::default()); 361 let func = CString::new(loc.function).unwrap_or(CString::default());
364 entry.as_ptr(), 374 entry.as_ptr(),
365 ) 375 )
366 } 376 }
367 } 377 }
368 } 378 }
369 379 }
380
381 impl PamShared for LibPamHandle {
370 fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString> { 382 fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString> {
371 let prompt = memory::option_cstr_os(prompt); 383 let prompt = memory::option_cstr_os(prompt);
372 let mut output: *const c_char = ptr::null(); 384 let mut output: *const c_char = ptr::null();
373 let ret = unsafe { 385 let ret = unsafe {
374 libpam_sys::pam_get_user( 386 libpam_sys::pam_get_user(
486 // Implementations of internal functions. 498 // Implementations of internal functions.
487 impl LibPamHandle { 499 impl LibPamHandle {
488 #[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))] 500 #[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))]
489 fn get_authtok(&mut self, prompt: Option<&OsStr>, item_type: ItemType) -> Result<OsString> { 501 fn get_authtok(&mut self, prompt: Option<&OsStr>, item_type: ItemType) -> Result<OsString> {
490 let prompt = memory::option_cstr_os(prompt); 502 let prompt = memory::option_cstr_os(prompt);
491 let mut output: *const c_char = ptr::null_mut(); 503 let mut output: *const c_char = ptr::null();
492 // SAFETY: We're calling this with known-good values. 504 // SAFETY: We're calling this with known-good values.
493 let res = unsafe { 505 let res = unsafe {
494 libpam_sys::pam_get_authtok( 506 libpam_sys::pam_get_authtok(
495 self.raw_mut(), 507 self.raw_mut(),
496 item_type.into(), 508 item_type.into(),
501 ErrorCode::result_from(res)?; 513 ErrorCode::result_from(res)?;
502 // SAFETY: We got this string from PAM. 514 // SAFETY: We got this string from PAM.
503 unsafe { memory::copy_pam_string(output) }.ok_or(ErrorCode::ConversationError) 515 unsafe { memory::copy_pam_string(output) }.ok_or(ErrorCode::ConversationError)
504 } 516 }
505 517
506 #[cfg(not(any(pam_impl = "LinuxPam", pam_impl = "OpenPam")))] 518 #[cfg(pam_impl = "Sun")]
507 fn get_authtok(&mut self, prompt: Option<&OsStr>, item_type: ItemType) -> Result<OsString> { 519 fn get_authtok(&mut self, prompt: Option<&OsStr>, item_type: ItemType) -> Result<OsString> {
508 Err(ErrorCode::ConversationError) 520 use crate::libpam::memory::CHeapString;
521 use std::os::unix::ffi::OsStringExt;
522 // Sun's __pam_get_authtok function is a little weird and requires
523 // that you specify where you want the authtok to come from.
524 // First we see if there's an authtok already set.
525 let mut output: *mut c_char = ptr::null_mut();
526 let result = unsafe {
527 libpam_sys::__pam_get_authtok(
528 self.raw_mut(),
529 libpam_sys::PAM_HANDLE,
530 item_type.into(),
531 ptr::null(),
532 &mut output,
533 )
534 };
535 let output = unsafe { CHeapString::from_ptr(output) };
536 if result == libpam_sys::PAM_SUCCESS {
537 if let Some(output) = output {
538 return Ok(OsString::from_vec(output.to_bytes().into()));
539 }
540 }
541 drop(output);
542 let mut output: *mut c_char = ptr::null_mut();
543 let prompt = memory::option_cstr_os(prompt);
544 let result = unsafe {
545 libpam_sys::__pam_get_authtok(
546 self.raw_mut(),
547 libpam_sys::PAM_PROMPT,
548 item_type.into(),
549 memory::prompt_ptr(prompt.as_deref()),
550 &mut output,
551 )
552 };
553 let output = unsafe { CHeapString::from_ptr(output) };
554 ErrorCode::result_from(result)?;
555 output
556 .map(|s| OsString::from_vec(s.to_bytes().into()))
557 .ok_or(ErrorCode::ConversationError)
509 } 558 }
510 559
511 /// Gets the `PAM_CONV` item from the handle. 560 /// Gets the `PAM_CONV` item from the handle.
512 fn conversation_item(&self) -> Result<&PamConv> { 561 fn conversation_item(&self) -> Result<&PamConv> {
513 let output: *const PamConv = ptr::null_mut(); 562 let output: *const PamConv = ptr::null_mut();
524 } 573 }
525 } 574 }
526 575
527 /// Identifies what is being gotten or set with `pam_get_item` 576 /// Identifies what is being gotten or set with `pam_get_item`
528 /// or `pam_set_item`. 577 /// or `pam_set_item`.
529 #[derive(TryFromPrimitive, IntoPrimitive)] 578 #[derive(Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
530 #[repr(i32)] 579 #[repr(i32)]
531 #[non_exhaustive] // because C could give us anything! 580 #[non_exhaustive] // because C could give us anything!
532 pub enum ItemType { 581 pub enum ItemType {
533 /// The PAM service name. 582 /// The PAM service name.
534 Service = constants::PAM_SERVICE, 583 Service = constants::PAM_SERVICE,