1use crate::env_list::EnvList;
12use crate::error::{Error, ErrorCode};
13use crate::ffi::to_pam_conv;
14use crate::session::{Session, SessionToken};
15use crate::{char_ptr_to_str, ConversationHandler};
16extern crate libc;
17extern crate pam_sys;
18
19use crate::{ExtResult, Flag, Result, PAM_SUCCESS};
20
21use libc::{c_char, c_int, c_void};
22use pam_sys::pam_handle_t as RawPamHandle;
23use pam_sys::{
24 pam_acct_mgmt, pam_authenticate, pam_chauthtok, pam_close_session, pam_end, pam_get_item,
25 pam_getenv, pam_getenvlist, pam_open_session, pam_putenv, pam_set_item, pam_setcred, pam_start,
26};
27use std::cell::Cell;
28use std::ffi::{CStr, CString, OsStr};
29use std::mem::take;
30use std::os::unix::ffi::OsStrExt;
31use std::ptr::NonNull;
32use std::{ptr, slice};
33
34macro_rules! impl_pam_str_item {
36 ($name:ident, $set_name:ident, $item_type:expr$(, $doc:literal$(, $extdoc:literal)?)?$(,)?) => {
37 $(#[doc = "Returns "]#[doc = $doc]$(#[doc = "\n\n"]#[doc = $extdoc])?)?
38 pub fn $name(&self) -> Result<String> {
39 let ptr = self.get_item($item_type as c_int)?;
40 if ptr.is_null() {
41 return Err(Error::new(self.handle(), ErrorCode::PERM_DENIED));
42 }
43 let string = unsafe { CStr::from_ptr(ptr.cast()) }.to_string_lossy().into_owned();
44 return Ok(string);
45 }
46
47 $(#[doc = "Sets "]#[doc = $doc])?
48 pub fn $set_name(&mut self, value: Option<&str>) -> Result<()> {
49 match value {
50 None => unsafe { self.set_item($item_type as c_int, ptr::null()) },
51 Some(string) => {
52 let cstring = CString::new(string).map_err(|_| Error::new(self.handle(), ErrorCode::BUF_ERR))?;
53 unsafe { self.set_item($item_type as c_int, cstring.as_ptr().cast()) }
54 }
55 }
56 }
57 }
58}
59
60#[derive(Debug, Clone, Copy)]
61pub(crate) struct PamHandle(NonNull<RawPamHandle>);
62
63impl PamHandle {
65 #[inline]
70 pub unsafe fn new(ptr: *mut RawPamHandle) -> Option<Self> {
71 NonNull::new(ptr).map(Self)
72 }
73
74 #[inline]
75 pub const fn as_ptr(self) -> *mut RawPamHandle {
76 self.0.as_ptr()
77 }
78}
79
80impl From<PamHandle> for *mut RawPamHandle {
81 #[inline]
82 fn from(handle: PamHandle) -> Self {
83 handle.as_ptr()
84 }
85}
86
87impl From<PamHandle> for *const RawPamHandle {
88 #[inline]
89 fn from(handle: PamHandle) -> Self {
90 handle.as_ptr()
91 }
92}
93
94#[cfg(any(target_os = "linux", doc))]
100#[repr(C)]
101#[derive(Debug)]
102struct XAuthData {
103 pub namelen: c_int,
105 pub name: *const c_char,
107 pub datalen: c_int,
109 pub data: *const c_char,
111}
112
113pub struct Context<ConvT>
119where
120 ConvT: ConversationHandler,
121{
122 handle: Option<PamHandle>,
123 conversation: Box<ConvT>,
125 last_status: Cell<c_int>,
126}
127
128impl<ConvT> Context<ConvT>
129where
130 ConvT: ConversationHandler,
131{
132 #[rustversion::attr(since(1.48), doc(alias = "pam_start"))]
154 pub fn new(service: &str, username: Option<&str>, conversation: ConvT) -> Result<Self> {
155 Self::from_boxed_conv(service, username, Box::new(conversation))
157 }
158
159 pub fn from_boxed_conv(
164 service: &str,
165 username: Option<&str>,
166 mut boxed_conv: Box<ConvT>,
167 ) -> Result<Self> {
168 let mut handle: *mut RawPamHandle = ptr::null_mut();
169
170 let c_service = CString::new(service).map_err(|_| Error::from(ErrorCode::BUF_ERR))?;
171 let c_username = match username {
172 None => None,
173 Some(name) => Some(CString::new(name).map_err(|_| Error::from(ErrorCode::BUF_ERR))?),
174 };
175
176 let pam_conv = to_pam_conv(&mut boxed_conv);
178
179 match unsafe {
181 pam_start(
182 c_service.as_ptr(),
183 c_username.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
184 &pam_conv,
185 &mut handle,
186 )
187 } {
188 PAM_SUCCESS => {
189 let handle = unsafe { PamHandle::new(handle) }
192 .ok_or_else(|| Error::from(ErrorCode::ABORT))?;
193 boxed_conv.init(username);
194 Ok(Self {
195 handle: Some(handle),
196 conversation: boxed_conv,
197 last_status: Cell::new(PAM_SUCCESS),
198 })
199 }
200 code => Err(ErrorCode::from_repr(code)
201 .unwrap_or(ErrorCode::ABORT)
202 .into()),
203 }
204 }
205
206 #[inline]
211 pub(crate) fn handle(&self) -> PamHandle {
212 self.handle.unwrap()
213 }
214
215 #[inline]
217 pub(crate) fn wrap_pam_return(&self, status: c_int) -> Result<()> {
218 self.last_status.set(status);
219 match status {
220 PAM_SUCCESS => Ok(()),
221 code => Err(Error::new(
222 self.handle(),
223 ErrorCode::from_repr(code).unwrap_or(ErrorCode::ABORT),
224 )),
225 }
226 }
227
228 pub fn conversation(&self) -> &ConvT {
230 &self.conversation
231 }
232
233 pub fn conversation_mut(&mut self) -> &mut ConvT {
235 &mut self.conversation
236 }
237
238 #[rustversion::attr(since(1.48), doc(alias = "pam_get_item"))]
249 pub fn get_item(&self, item_type: c_int) -> Result<*const c_void> {
250 let mut result: *const c_void = ptr::null();
251 self.wrap_pam_return(unsafe {
252 pam_get_item(self.handle().into(), item_type, &mut result)
253 })?;
254 Ok(result)
255 }
256
257 #[rustversion::attr(since(1.48), doc(alias = "pam_set_item"))]
274 pub unsafe fn set_item(&mut self, item_type: c_int, value: *const c_void) -> Result<()> {
275 self.wrap_pam_return(pam_set_item(self.handle().into(), item_type, &*value))
276 }
277
278 impl_pam_str_item!(
279 service,
280 set_service,
281 pam_sys::PAM_SERVICE,
282 "the service name"
283 );
284 impl_pam_str_item!(user, set_user, pam_sys::PAM_USER, "the username of the entity under whose identity service will be given",
285 "This value can be mapped by any module in the PAM stack, so don't assume it stays unchanged after calling other methods on `Self`.");
286 impl_pam_str_item!(
287 user_prompt,
288 set_user_prompt,
289 pam_sys::PAM_USER_PROMPT,
290 "the string used when prompting for a user's name"
291 );
292 impl_pam_str_item!(tty, set_tty, pam_sys::PAM_TTY, "the terminal name");
293 impl_pam_str_item!(
294 ruser,
295 set_ruser,
296 pam_sys::PAM_RUSER,
297 "the requesting user name"
298 );
299 impl_pam_str_item!(
300 rhost,
301 set_rhost,
302 pam_sys::PAM_RHOST,
303 "the requesting hostname"
304 );
305 #[cfg(any(target_os = "linux", doc))]
306 impl_pam_str_item!(
307 authtok_type,
308 set_authtok_type,
309 pam_sys::PAM_AUTHTOK_TYPE,
310 "the default password type in the prompt (Linux specific)",
311 "E.g. \"UNIX\" for \"Enter UNIX password:\""
312 );
313 #[cfg(any(target_os = "linux", doc))]
314 impl_pam_str_item!(
315 xdisplay,
316 set_xdisplay,
317 pam_sys::PAM_XDISPLAY,
318 "the name of the X display (Linux specific)"
319 );
320
321 #[cfg(any(target_os = "linux", doc))]
323 pub fn xauthdata(&self) -> Result<(&CStr, &[u8])> {
324 let handle = self.handle();
325 let ptr = self
326 .get_item(pam_sys::PAM_XAUTHDATA as c_int)?
327 .cast::<XAuthData>();
328 if ptr.is_null() {
329 return Err(Error::new(handle, ErrorCode::PERM_DENIED));
330 }
331 let data = unsafe { &*ptr };
332
333 if data.namelen < 0 || data.datalen < 0 || data.name.is_null() || data.data.is_null() {
336 return Err(Error::new(handle, ErrorCode::BUF_ERR));
337 }
338
339 #[allow(clippy::cast_sign_loss)]
340 Ok((
341 CStr::from_bytes_with_nul(unsafe {
342 slice::from_raw_parts(data.name.cast(), data.namelen as usize + 1)
343 })
344 .map_err(|_| Error::new(handle, ErrorCode::BUF_ERR))?,
345 unsafe { slice::from_raw_parts(data.data.cast(), data.datalen as usize) },
346 ))
347 }
348
349 #[cfg(any(target_os = "linux", doc))]
356 pub fn set_xauthdata(&mut self, value: Option<(&CStr, &[u8])>) -> Result<()> {
357 match value {
358 None => unsafe { self.set_item(pam_sys::PAM_XAUTHDATA as c_int, ptr::null()) },
359 Some((name, data)) => {
360 let name_bytes = name.to_bytes_with_nul();
361
362 if name_bytes.len() > i32::MAX as usize || data.len() > i32::MAX as usize {
363 return Err(Error::new(self.handle(), ErrorCode::BUF_ERR));
364 }
365
366 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
367 let xauthdata = XAuthData {
368 namelen: name_bytes.len() as i32 - 1,
369 name: name_bytes.as_ptr().cast(),
370 datalen: data.len() as i32,
371 data: data.as_ptr().cast(),
372 };
373 unsafe {
374 self.set_item(
375 pam_sys::PAM_XAUTHDATA as c_int,
376 &xauthdata as *const _ as *const c_void,
377 )
378 }
379 }
380 }
381 }
382
383 #[must_use]
388 #[rustversion::attr(since(1.48), doc(alias = "pam_getenv"))]
389 pub fn getenv(&self, name: impl AsRef<OsStr>) -> Option<&str> {
390 let c_name = match CString::new(name.as_ref().as_bytes()) {
391 Err(_) => return None,
392 Ok(s) => s,
393 };
394 char_ptr_to_str(unsafe { pam_getenv(self.handle().into(), c_name.as_ptr()) })
395 }
396
397 #[rustversion::attr(since(1.48), doc(alias = "pam_putenv"))]
407 pub fn putenv(&mut self, name_value: impl AsRef<OsStr>) -> Result<()> {
408 let c_name_value = CString::new(name_value.as_ref().as_bytes())
409 .map_err(|_| Error::from(ErrorCode::BUF_ERR))?;
410 self.wrap_pam_return(unsafe { pam_putenv(self.handle().into(), c_name_value.as_ptr()) })
411 }
412
413 #[must_use]
423 #[rustversion::attr(since(1.48), doc(alias = "pam_getenvlist"))]
424 pub fn envlist(&self) -> EnvList {
425 unsafe { EnvList::new(pam_getenvlist(self.handle().into()).cast()) }
426 }
427
428 #[rustversion::attr(since(1.48), doc(alias = "pam_authenticate"))]
454 pub fn authenticate(&mut self, flags: Flag) -> Result<()> {
455 self.wrap_pam_return(unsafe { pam_authenticate(self.handle().into(), flags.bits()) })
456 }
457
458 #[rustversion::attr(since(1.48), doc(alias = "pam_acct_mgmt"))]
481 pub fn acct_mgmt(&mut self, flags: Flag) -> Result<()> {
482 self.wrap_pam_return(unsafe { pam_acct_mgmt(self.handle().into(), flags.bits()) })
483 }
484
485 pub fn reinitialize_credentials(&mut self, flags: Flag) -> Result<()> {
502 self.wrap_pam_return(unsafe {
503 pam_setcred(
504 self.handle().into(),
505 (Flag::REINITIALIZE_CRED | flags).bits(),
506 )
507 })
508 }
509
510 #[rustversion::attr(since(1.48), doc(alias = "pam_chauthtok"))]
533 pub fn chauthtok(&mut self, flags: Flag) -> Result<()> {
534 self.wrap_pam_return(unsafe { pam_chauthtok(self.handle().into(), flags.bits()) })
535 }
536
537 #[rustversion::attr(since(1.48), doc(alias = "pam_open_session"))]
568 pub fn open_session(&mut self, flags: Flag) -> Result<Session<ConvT>> {
569 let handle = self.handle().as_ptr();
570 self.wrap_pam_return(unsafe {
571 pam_setcred(handle, (Flag::ESTABLISH_CRED | flags).bits())
572 })?;
573
574 if let Err(e) = self.wrap_pam_return(unsafe { pam_open_session(handle, flags.bits()) }) {
575 let _ = self.wrap_pam_return(unsafe {
576 pam_setcred(handle, (Flag::DELETE_CRED | flags).bits())
577 });
578 return Err(e);
579 }
580
581 if let Err(e) = self.wrap_pam_return(unsafe {
586 pam_setcred(handle, (Flag::REINITIALIZE_CRED | flags).bits())
587 }) {
588 let _ = self.wrap_pam_return(unsafe { pam_close_session(handle, flags.bits()) });
589 let _ = self.wrap_pam_return(unsafe {
590 pam_setcred(handle, (Flag::DELETE_CRED | flags).bits())
591 });
592 return Err(e);
593 }
594
595 Ok(Session::new(self, true))
596 }
597
598 pub fn open_pseudo_session(&mut self, flags: Flag) -> Result<Session<ConvT>> {
621 self.wrap_pam_return(unsafe {
622 pam_setcred(self.handle().into(), (Flag::ESTABLISH_CRED | flags).bits())
623 })?;
624
625 Ok(Session::new(self, false))
626 }
627
628 pub fn unleak_session(&mut self, token: SessionToken) -> Session<ConvT> {
630 Session::new(self, matches!(token, SessionToken::FullSession))
631 }
632}
633
634impl<ConvT> Context<ConvT>
635where
636 ConvT: ConversationHandler + Default,
637{
638 pub fn replace_conversation<T: ConversationHandler>(
650 self,
651 new_handler: T,
652 ) -> ExtResult<(Context<T>, ConvT), (Self, T)> {
653 match self.replace_conversation_boxed(new_handler.into()) {
654 Ok((context, boxed_old_conv)) => Ok((context, *boxed_old_conv)),
655 Err(error) => Err(error.map(|(ctx, b_conv)| (ctx, *b_conv))),
656 }
657 }
658
659 pub fn replace_conversation_boxed<T: ConversationHandler>(
663 mut self,
664 mut new_handler: Box<T>,
665 ) -> ExtResult<(Context<T>, Box<ConvT>), (Self, Box<T>)> {
666 let username = match self.user() {
668 Ok(u) => Some(u),
669 Err(e) => {
670 if e.code() != ErrorCode::PERM_DENIED {
671 return Err(e.into_with_payload((self, new_handler)));
672 }
673 None
674 }
675 };
676 let pam_conv = to_pam_conv(&mut new_handler);
678 if let Err(e) = unsafe {
679 self.set_item(
680 pam_sys::PAM_CONV as c_int,
681 &pam_conv as *const _ as *const _,
682 )
683 } {
684 Err(e.into_with_payload((self, new_handler)))
685 } else {
686 new_handler.init(username);
688 Ok((
690 Context::<T> {
691 handle: self.handle.take(),
692 conversation: new_handler,
693 last_status: Cell::new(self.last_status.replace(PAM_SUCCESS)),
694 },
695 take(&mut self.conversation),
696 ))
697 }
698 }
699}
700
701impl<ConvT> Drop for Context<ConvT>
703where
704 ConvT: ConversationHandler,
705{
706 #[rustversion::attr(since(1.48), doc(alias = "pam_end"))]
707 fn drop(&mut self) {
708 if let Some(handle) = self.handle {
709 unsafe { pam_end(handle.into(), self.last_status.get()) };
710 }
711 }
712}
713
714unsafe impl<ConvT> Send for Context<ConvT> where ConvT: ConversationHandler + Send {}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722 use std::ffi::{OsStr, OsString};
723
724 #[test]
725 fn test_basic() {
726 let mut context =
727 Context::new("test", Some("user"), crate::conv_null::Conversation::new()).unwrap();
728 assert_eq!(context.service().unwrap(), "test");
730 assert_eq!(context.user().unwrap(), "user");
731 let h = context.handle();
733 assert_eq!(&h.clone().0, &h.0);
734 assert!(format!("{:?}", h).contains(&format!("{:?}", h.as_ptr())));
735 context.set_user_prompt(Some("Who art thou? ")).unwrap();
737 assert_eq!(context.user_prompt().unwrap(), "Who art thou? ");
738 context.set_tty(Some("/dev/tty")).unwrap();
739 assert_eq!(context.tty().unwrap(), "/dev/tty");
740 context.set_ruser(Some("nobody")).unwrap();
741 assert_eq!(context.ruser().unwrap(), "nobody");
742 context.set_rhost(Some("nowhere")).unwrap();
743 assert_eq!(context.rhost().unwrap(), "nowhere");
744 #[cfg(target_os = "linux")]
746 {
747 context.set_authtok_type(Some("TEST")).unwrap();
748 assert_eq!(context.authtok_type().unwrap(), "TEST");
749 context.set_xdisplay(Some(":0")).unwrap();
750 assert_eq!(context.xdisplay().unwrap(), ":0");
751 let xauthname = CString::new("TEST_DATA").unwrap();
752 let xauthdata = [];
753 let _ = context.xauthdata();
754 context
755 .set_xauthdata(Some((&xauthname, &xauthdata)))
756 .unwrap();
757 let (resultname, resultdata) = context.xauthdata().unwrap();
758 assert_eq!(resultname, xauthname.as_c_str());
759 assert_eq!(resultdata, &xauthdata);
760 };
761 assert_eq!(
763 context.conversation_mut() as *mut _ as *const _,
764 context.conversation() as *const _
765 );
766 context
767 .conversation_mut()
768 .text_info(&CString::new("").unwrap());
769 assert!(context.get_item(pam_sys::PAM_AUTHTOK as c_int).is_err());
771 context.putenv("TEST=1").unwrap();
773 context.putenv("TEST2=2").unwrap();
774 let _ = context.putenv("\0=\0").unwrap_err();
775 assert_eq!(context.getenv("TEST").unwrap(), "1");
776 assert!(context.getenv("TESTNONEXIST").is_none());
777 let env = context.envlist();
778 assert!(env.len() > 0);
779 let _ = env.get("TEST").unwrap();
780 let _ = env.get("TESTNONEXIST").is_none();
781 for (key, value) in env.iter_tuples() {
782 if key.to_string_lossy() == "TEST" {
783 assert_eq!(value.to_string_lossy(), "1");
784 }
785 }
786 assert!(format!("{:?}", &env.iter_tuples()).contains("EnvItem"));
787 for item in &env {
788 let string = item.to_string();
789 if string.starts_with("TEST=") {
790 assert_eq!(string, "TEST=1");
791 assert!(format!("{:?}", &item).contains("EnvItem"));
792 } else if string.starts_with("TEST2=") {
793 let (_, v): (&OsStr, &OsStr) = item.into();
794 assert_eq!(v.to_string_lossy(), "2");
795 }
796 let _ = item.as_ref();
797 }
798 let _ = format!("{:?}", &env);
799 assert_eq!(env.is_empty(), false);
800 assert_eq!(env.len(), env.as_ref().len());
801 assert_eq!(env.as_ref(), context.envlist().as_ref());
802 assert_eq!(
803 env.as_ref().partial_cmp(context.envlist().as_ref()),
804 Some(std::cmp::Ordering::Equal)
805 );
806 assert_eq!(
807 env.as_ref().cmp(context.envlist().as_ref()),
808 std::cmp::Ordering::Equal
809 );
810 assert_eq!(&env["TEST"], "1");
811 assert_eq!(env.len(), env.iter_tuples().size_hint().0);
812 let list: std::vec::Vec<&CStr> = (&env).into();
813 assert_eq!(list.len(), env.len());
814 let list: std::vec::Vec<(&OsStr, _)> = (&env).into();
815 assert_eq!(list.len(), env.len());
816 let map: std::collections::HashMap<&OsStr, _> = (&env).into();
817 assert_eq!(map.len(), map.len());
818 assert_eq!(
819 map.get(&OsString::from("TEST".to_string()).as_ref()),
820 Some(&OsString::from("1".to_string()).as_ref())
821 );
822 assert!(env.to_string().contains("TEST=1"));
823 let list: std::vec::Vec<(std::ffi::OsString, _)> = context.envlist().into();
824 assert_eq!(list.len(), env.len());
825 let list: std::vec::Vec<CString> = context.envlist().into();
826 assert_eq!(list.len(), env.len());
827 let map: std::collections::HashMap<_, _> = context.envlist().into();
828 assert_eq!(map.len(), env.len());
829 assert_eq!(
830 map.get(&OsString::from("TEST".to_string())),
831 Some(&OsString::from("1".to_string()))
832 );
833 drop(context)
834 }
835
836 #[test]
837 fn test_conv_replace() {
838 let mut context =
839 Context::new("test", Some("user"), crate::conv_null::Conversation::new()).unwrap();
840 context.set_user(Some("anybody")).unwrap();
842 let (mut context, old_conv) = context
844 .replace_conversation(crate::conv_mock::Conversation::default())
845 .unwrap();
846 assert_eq!(context.conversation().username, "anybody");
848 context.set_user(None).unwrap();
849 let (context, _) = context.replace_conversation(old_conv).unwrap();
850 assert!(context.user().is_err());
852 }
853
854 #[test]
861 #[cfg_attr(not(feature = "full_test"), ignore)]
862 fn test_full() {
863 let mut context = Context::new(
864 "test_rust_pam_client",
865 Some("nobody"),
866 crate::conv_null::Conversation::new(),
867 )
868 .unwrap();
869 let _ = context.authenticate(Flag::SILENT);
870 let _ = context.acct_mgmt(Flag::SILENT);
871 let _ = context.chauthtok(Flag::CHANGE_EXPIRED_AUTHTOK);
872 let _ = context.reinitialize_credentials(Flag::SILENT | Flag::NONE);
873 drop(context.open_session(Flag::SILENT));
874 drop(context.open_pseudo_session(Flag::SILENT));
875 }
876
877 #[test]
886 #[cfg_attr(not(feature = "full_test"), ignore)]
887 fn test_full_unauth() {
888 let mut context = Context::new(
889 "test_rust_pam_client",
890 Some("nobody"),
891 crate::conv_null::Conversation::new(),
892 )
893 .unwrap();
894 let _ = context.acct_mgmt(Flag::SILENT);
895 let _ = context.chauthtok(Flag::CHANGE_EXPIRED_AUTHTOK);
896 let _ = context.reinitialize_credentials(Flag::SILENT | Flag::NONE);
897 if let Ok(mut session) = context.open_session(Flag::SILENT) {
898 let _ = session.refresh_credentials(Flag::SILENT);
899 let _ = session.reinitialize_credentials(Flag::SILENT);
900 let _ = session.envlist();
901 let _ = session.close(Flag::SILENT);
902 };
903 if let Ok(mut session) = context.open_pseudo_session(Flag::SILENT) {
904 let _ = session.refresh_credentials(Flag::SILENT);
905 };
906 }
907}