view src/libpam/handle.rs @ 87:05291b601f0a

Well and truly separate the Linux extensions. This separates the Linux extensions on the libpam side, and disables the two enums on the interface side. Users can still call the Linux extensions from non-Linux PAM impls, but they'll get a conversation error back.
author Paul Fisher <paul@pfish.zone>
date Tue, 10 Jun 2025 04:40:01 -0400
parents 5aa1a010f1e8
children
line wrap: on
line source

use super::conversation::LibPamConversation;
use crate::constants::{ErrorCode, Result};
use crate::conv::Message;
use crate::handle::PamShared;
pub use crate::libpam::pam_ffi::LibPamHandle;
use crate::libpam::{memory, pam_ffi};
use crate::{Conversation, PamHandleModule};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::cell::Cell;
use std::ffi::{c_char, c_int};
use std::ops::{Deref, DerefMut};
use std::ptr;

struct HandleWrap(*mut LibPamHandle);

impl Deref for HandleWrap {
    type Target = LibPamHandle;
    fn deref(&self) -> &Self::Target {
        unsafe { &*self.0 }
    }
}

impl DerefMut for HandleWrap {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { &mut *self.0 }
    }
}

/// An owned PAM handle.
pub struct OwnedLibPamHandle {
    handle: HandleWrap,
    last_return: Cell<Result<()>>,
}

// TODO: pam_authenticate - app
//       pam_setcred - app
//       pam_acct_mgmt - app
//       pam_chauthtok - app
//       pam_open_session - app
//       pam_close_session - app
//       pam_putenv - shared
//       pam_getenv - shared
//       pam_getenvlist - shared

impl Drop for OwnedLibPamHandle {
    /// Closes the PAM session on an owned PAM handle.
    ///
    /// See the [`pam_end` manual page][man] for more information.
    ///
    /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html
    fn drop(&mut self) {
        unsafe {
            pam_ffi::pam_end(
                self.handle.0,
                ErrorCode::result_to_c(self.last_return.get()),
            );
        }
    }
}

macro_rules! cstr_item {
    (get = $getter:ident, item = $item_type:path) => {
        fn $getter(&self) -> Result<Option<&str>> {
            unsafe { self.get_cstr_item($item_type) }
        }
    };
    (set = $setter:ident, item = $item_type:path) => {
        fn $setter(&mut self, value: Option<&str>) -> Result<()> {
            unsafe { self.set_cstr_item($item_type, value) }
        }
    };
}

impl PamShared for LibPamHandle {
    fn get_user(&mut self, prompt: Option<&str>) -> Result<&str> {
        let prompt = memory::option_cstr(prompt)?;
        let mut output: *const c_char = ptr::null();
        let ret = unsafe {
            pam_ffi::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref()))
        };
        ErrorCode::result_from(ret)?;
        unsafe { memory::wrap_string(output) }
            .transpose()
            .unwrap_or(Err(ErrorCode::ConversationError))
    }

    cstr_item!(get = user_item, item = ItemType::User);
    cstr_item!(set = set_user_item, item = ItemType::User);
    cstr_item!(get = service, item = ItemType::Service);
    cstr_item!(set = set_service, item = ItemType::Service);
    cstr_item!(get = user_prompt, item = ItemType::UserPrompt);
    cstr_item!(set = set_user_prompt, item = ItemType::UserPrompt);
    cstr_item!(get = tty_name, item = ItemType::Tty);
    cstr_item!(set = set_tty_name, item = ItemType::Tty);
    cstr_item!(get = remote_user, item = ItemType::RemoteUser);
    cstr_item!(set = set_remote_user, item = ItemType::RemoteUser);
    cstr_item!(get = remote_host, item = ItemType::RemoteHost);
    cstr_item!(set = set_remote_host, item = ItemType::RemoteHost);
    cstr_item!(set = set_authtok_item, item = ItemType::AuthTok);
    cstr_item!(set = set_old_authtok_item, item = ItemType::OldAuthTok);
}

impl Conversation for LibPamHandle {
    fn communicate(&mut self, messages: &[Message]) {
        match self.conversation_item() {
            Ok(conv) => conv.communicate(messages),
            Err(e) => {
                for msg in messages {
                    msg.set_error(e)
                }
            }
        }
    }
}

impl PamHandleModule for LibPamHandle {
    fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str> {
        let prompt = memory::option_cstr(prompt)?;
        let mut output: *const c_char = ptr::null_mut();
        // SAFETY: We're calling this with known-good values.
        let res = unsafe {
            pam_ffi::pam_get_authtok(
                self,
                ItemType::AuthTok.into(),
                &mut output,
                memory::prompt_ptr(prompt.as_ref()),
            )
        };
        ErrorCode::result_from(res)?;
        // SAFETY: We got this string from PAM.
        unsafe { memory::wrap_string(output) }
            .transpose()
            .unwrap_or(Err(ErrorCode::ConversationError))
    }

    cstr_item!(get = authtok_item, item = ItemType::AuthTok);
    cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok);
}

/// Function called at the end of a PAM session that is called to clean up
/// a value previously provided to PAM in a `pam_set_data` call.
///
/// You should never call this yourself.
extern "C" fn set_data_cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) {
    unsafe {
        let _data: Box<T> = Box::from_raw(c_data.cast());
    }
}

impl LibPamHandle {
    /// Gets a C string item.
    ///
    /// # Safety
    ///
    /// You better be requesting an item which is a C string.
    unsafe fn get_cstr_item(&self, item_type: ItemType) -> Result<Option<&str>> {
        let mut output = ptr::null();
        let ret = unsafe { pam_ffi::pam_get_item(self, item_type as c_int, &mut output) };
        ErrorCode::result_from(ret)?;
        memory::wrap_string(output.cast())
    }

    /// Sets a C string item.
    ///
    /// # Safety
    ///
    /// You better be setting an item which is a C string.
    unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> {
        let data_str = memory::option_cstr(data)?;
        let ret = unsafe {
            pam_ffi::pam_set_item(
                self,
                item_type as c_int,
                memory::prompt_ptr(data_str.as_ref()).cast(),
            )
        };
        ErrorCode::result_from(ret)
    }

    /// Gets the `PAM_CONV` item from the handle.
    fn conversation_item(&mut self) -> Result<&mut LibPamConversation<'_>> {
        let output: *mut LibPamConversation = ptr::null_mut();
        let result = unsafe {
            pam_ffi::pam_get_item(
                self,
                ItemType::Conversation.into(),
                &mut output.cast_const().cast(),
            )
        };
        ErrorCode::result_from(result)?;
        // SAFETY: We got this result from PAM, and we're checking if it's null.
        unsafe { output.as_mut() }.ok_or(ErrorCode::ConversationError)
    }
}

macro_rules! delegate {
    (fn $meth:ident(&self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => {
        fn $meth(&self $(, $param: $typ)*) -> Result<$ret> {
            let result = self.handle.$meth($($param),*);
            self.last_return.set(split(&result));
            result
        }
    };
    (fn $meth:ident(&mut self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => {
        fn $meth(&mut self $(, $param: $typ)*) -> Result<$ret> {
            let result = self.handle.$meth($($param),*);
            self.last_return.set(split(&result));
            result
        }
    };
    (get = $get:ident$(, set = $set:ident)?) => {
        delegate!(fn $get(&self) -> Result<Option<&str>>);
        $(delegate!(set = $set);)?
    };
    (set = $set:ident) => {
        delegate!(fn $set(&mut self, value: Option<&str>) -> Result<()>);
    };
}

fn split<T>(result: &Result<T>) -> Result<()> {
    result.as_ref().map(drop).map_err(|&e| e)
}

impl PamShared for OwnedLibPamHandle {
    delegate!(fn get_user(&mut self, prompt: Option<&str>) -> Result<&str>);
    delegate!(get = user_item, set = set_user_item);
    delegate!(get = service, set = set_service);
    delegate!(get = user_prompt, set = set_user_prompt);
    delegate!(get = tty_name, set = set_tty_name);
    delegate!(get = remote_user, set = set_remote_user);
    delegate!(get = remote_host, set = set_remote_host);
    delegate!(set = set_authtok_item);
    delegate!(set = set_old_authtok_item);
}

/// Identifies what is being gotten or set with `pam_get_item`
/// or `pam_set_item`.
#[derive(TryFromPrimitive, IntoPrimitive)]
#[repr(i32)]
#[non_exhaustive] // because C could give us anything!
pub enum ItemType {
    /// The PAM service name.
    Service = 1,
    /// The user's login name.
    User = 2,
    /// The TTY name.
    Tty = 3,
    /// The remote host (if applicable).
    RemoteHost = 4,
    /// The conversation struct (not a CStr-based item).
    Conversation = 5,
    /// The authentication token (password).
    AuthTok = 6,
    /// The old authentication token (when changing passwords).
    OldAuthTok = 7,
    /// The remote user's name.
    RemoteUser = 8,
    /// The prompt shown when requesting a username.
    UserPrompt = 9,
    /// App-supplied function to override failure delays.
    FailDelay = 10,
    /// X display name.
    XDisplay = 11,
    /// X server authentication data.
    XAuthData = 12,
    /// The type of `pam_get_authtok`.
    AuthTokType = 13,
}