view src/conv.rs @ 65:8e507c7af9cf default tip

Added tag v0.0.5 for changeset bbe84835d6db
author Paul Fisher <paul@pfish.zone>
date Thu, 22 May 2025 02:08:10 -0400
parents bbe84835d6db
children
line wrap: on
line source

//! The [Conversation] struct, for interacting with the user.
//!
//! This module is experimental and will probably be rewritten in the future
//! to improve the interface for both PAM modules and clients.

use crate::constants::MessageStyle;
use crate::constants::Result;
use crate::constants::{ErrorCode, InvalidEnum};
use crate::items::Item;
use libc::{c_char, c_int};
use num_derive::FromPrimitive;
use std::ffi::{CStr, CString};
use std::ptr;

/// Styles of message that are shown to the user.
#[derive(Debug, PartialEq, FromPrimitive)]
#[non_exhaustive] // non-exhaustive because C might give us back anything!
pub enum MessageStyle {
    /// Requests information from the user; will be masked when typing.
    PromptEchoOff = 1,
    /// Requests information from the user; will not be masked.
    PromptEchoOn = 2,
    /// An error message.
    ErrorMsg = 3,
    /// An informational message.
    TextInfo = 4,
    /// Yes/No/Maybe conditionals. Linux-PAM specific.
    RadioType = 5,
    /// For server–client non-human interaction.
    /// NOT part of the X/Open PAM specification.
    BinaryPrompt = 7,
}

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

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

#[repr(C)]
struct Message {
    msg_style: MessageStyle,
    msg: *const c_char,
}

#[repr(C)]
struct Response {
    resp: *const c_char,
    resp_retcode: libc::c_int, // Unused - always zero
}

#[doc(hidden)]
#[repr(C)]
pub struct Inner {
    conv: extern "C" fn(
        num_msg: c_int,
        pam_message: &&Message,
        pam_response: &mut *const Response,
        appdata_ptr: *const libc::c_void,
    ) -> c_int,
    appdata_ptr: *const libc::c_void,
}

/// A communication channel with the user.
///
/// Use this to communicate with the user, if needed, beyond the standard
/// things you can get/set with `get_user`/`get_authtok` and friends.
/// The PAM client (i.e., the application that is logging in) will present
/// the messages you send to the user and ask for responses.
pub struct Conversation<'a>(&'a Inner);

impl Conversation<'_> {
    /// Sends a message to the PAM client.
    ///
    /// This will typically result in the user seeing a message or a prompt.
    /// For details, see what [MessageStyle]s are available.
    ///
    /// Note that the user experience will depend on how each style
    /// is implemented by the client, and that not all clients
    /// will implement all message styles.
    pub fn send(&self, style: MessageStyle, msg: &str) -> Result<Option<&CStr>> {
        let mut resp_ptr: *const Response = ptr::null();
        let msg_cstr = CString::new(msg).unwrap();
        let msg = Message {
            msg_style: style,
            msg: msg_cstr.as_ptr(),
        };
        // TODO: These need to be freed!
        let ret = (self.0.conv)(1, &&msg, &mut resp_ptr, self.0.appdata_ptr);
        ErrorCode::result_from(ret)?;

        let result = unsafe {
            match (*resp_ptr).resp {
                p if p.is_null() => None,
                p => Some(CStr::from_ptr(p)),
            }
        };
        Ok(result)
    }
}

impl Item for Conversation<'_> {
    type Raw = Inner;

    fn type_id() -> crate::items::ItemType {
        crate::items::ItemType::Conversation
    }

    unsafe fn from_raw(raw: *const Self::Raw) -> Self {
        Self(&*raw)
    }

    fn into_raw(self) -> *const Self::Raw {
        self.0 as _
    }
}