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