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, |