view src/conv.rs @ 62:d83623951070

Further improve docs and put `conv` behind a feature gate.
author Paul Fisher <paul@pfish.zone>
date Wed, 21 May 2025 23:10:09 -0400
parents 05cc2c27334f
children a7aa5ca0d00d
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 libc::{c_char, c_int};
use std::ffi::{CStr, CString};
use std::ptr;

use crate::constants::ErrorCode;
use crate::constants::MessageStyle;
use crate::constants::Result;
use crate::items::Item;

#[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 _
    }
}