view src/pam_ffi/handle.rs @ 73:ac6881304c78

Do conversations, along with way too much stuff. This implements conversations, along with all the memory management brouhaha that goes along with it. The conversation now lives directly on the handle rather than being a thing you have to get from it and then call manually. It Turns Out this makes things a lot easier! I guess we reorganized things again. For the last time. For real. I promise. This all passes ASAN, so it seems Pretty Good!
author Paul Fisher <paul@pfish.zone>
date Thu, 05 Jun 2025 03:41:38 -0400
parents
children c7c596e6388f
line wrap: on
line source

use super::conversation::LibPamConversation;
use crate::constants::{ErrorCode, InvalidEnum, Result};
use crate::conv::Message;
use crate::handle::{PamApplicationOnly, PamModuleOnly, PamShared};
use crate::pam_ffi::memory;
use crate::pam_ffi::memory::Immovable;
use crate::{Conversation, Response};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use std::ffi::{c_char, c_int};
use std::ops::{Deref, DerefMut};
use std::result::Result as StdResult;
use std::{mem, ptr};

/// An owned PAM handle.
#[repr(transparent)]
pub struct OwnedLibPamHandle(*mut LibPamHandle);

/// An opaque structure that a PAM handle points to.
#[repr(C)]
pub struct LibPamHandle {
    _data: (),
    _marker: Immovable,
}

impl LibPamHandle {
    /// Gets a C string item.
    ///
    /// # Safety
    ///
    /// You better be requesting an item which is a C string.
    unsafe fn get_cstr_item(&mut self, item_type: ItemType) -> Result<Option<&str>> {
        let mut output = ptr::null();
        let ret = unsafe { super::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 {
            super::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 {
            super::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)
    }
}

impl PamApplicationOnly for OwnedLibPamHandle {
    fn close(self, status: Result<()>) -> Result<()> {
        let ret = unsafe { super::pam_end(self.0, ErrorCode::result_to_c(status)) };
        // Forget rather than dropping, since dropping also calls pam_end.
        mem::forget(self);
        ErrorCode::result_from(ret)
    }
}

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

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

impl Drop for OwnedLibPamHandle {
    /// Ends the PAM session with a zero error code.
    /// You probably want to call [`close`](Self::close) instead of
    /// letting this drop by itself.
    fn drop(&mut self) {
        unsafe {
            super::pam_end(self.0, 0);
        }
    }
}

macro_rules! cstr_item {
    (get = $getter:ident, item = $item_type:path) => {
        fn $getter(&mut 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 { super::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 converse(&mut self, messages: &[Message]) -> Result<Vec<Response>> {
        self.conversation_item()?.converse(messages)
    }
}

impl PamModuleOnly 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 {
            super::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());
    }
}

/// Identifies what is being gotten or set with `pam_get_item`
/// or `pam_set_item`.
#[derive(FromPrimitive)]
#[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,
}

impl TryFrom<c_int> for ItemType {
    type Error = InvalidEnum<Self>;
    fn try_from(value: c_int) -> StdResult<Self, Self::Error> {
        Self::from_i32(value).ok_or(value.into())
    }
}

impl From<ItemType> for c_int {
    fn from(val: ItemType) -> Self {
        val as Self
    }
}