pam_f/
conv.rs

1use libc::{c_int, c_void, calloc, free, size_t, strdup};
2
3use std::ffi::{CStr, CString};
4use std::mem;
5
6use crate::{ffi::pam_conv, PamMessage, PamMessageStyle, PamResponse, PamReturnCode};
7
8/// A trait representing the PAM authentification conversation
9///
10/// PAM authentification is done as a conversation mechanism, in which PAM
11/// asks several questions and the client (your code) answers them. This trait
12/// is a representation of such a conversation, which one method for each message
13/// PAM can send you.
14///
15/// This is the trait to implement if you want to customize the conversation with
16/// PAM. If you just want a simple login/password authentication, you can use the
17/// `PasswordConv` implementation provided by this crate.
18pub trait Conversation {
19    /// PAM requests a value that should be echoed to the user as they type it
20    ///
21    /// This would typically be the username. The exact question is provided as the
22    /// `msg` argument if you wish to display it to your user.
23    #[allow(clippy::result_unit_err)]
24    fn prompt_echo(&mut self, msg: &CStr) -> Result<CString, ()>;
25    /// PAM requests a value that should be typed blindly by the user
26    ///
27    /// This would typically be the password. The exact question is provided as the
28    /// `msg` argument if you wish to display it to your user.
29    #[allow(clippy::result_unit_err)]
30    fn prompt_blind(&mut self, msg: &CStr) -> Result<CString, ()>;
31    /// This is an informational message from PAM
32    fn info(&mut self, msg: &CStr);
33    /// This is an error message from PAM
34    fn error(&mut self, msg: &CStr);
35}
36
37/// A minimalistic conversation handler, that uses given login and password
38///
39/// This conversation handler is not really interactive, but simply returns to
40/// PAM the value that have been set using the `set_credentials` method.
41pub struct PasswordConv {
42    login: String,
43    passwd: String,
44}
45
46impl PasswordConv {
47    /// Create a new `PasswordConv` handler
48    pub(crate) fn new() -> PasswordConv {
49        PasswordConv {
50            login: String::new(),
51            passwd: String::new(),
52        }
53    }
54
55    /// Set the credentials that this handler will provide to PAM
56    pub fn set_credentials<U: Into<String>, V: Into<String>>(&mut self, login: U, password: V) {
57        self.login = login.into();
58        self.passwd = password.into();
59    }
60}
61
62impl Conversation for PasswordConv {
63    fn prompt_echo(&mut self, _msg: &CStr) -> Result<CString, ()> {
64        CString::new(self.login.clone()).map_err(|_| ())
65    }
66    fn prompt_blind(&mut self, _msg: &CStr) -> Result<CString, ()> {
67        CString::new(self.passwd.clone()).map_err(|_| ())
68    }
69    fn info(&mut self, _msg: &CStr) {}
70    fn error(&mut self, msg: &CStr) {
71        eprintln!("[PAM ERROR] {}", msg.to_string_lossy());
72    }
73}
74
75pub(crate) fn into_pam_conv<C: Conversation>(conv: &mut C) -> pam_conv {
76    pam_conv {
77        conv: Some(converse::<C>),
78        appdata_ptr: conv as *mut C as *mut c_void,
79    }
80}
81
82// FIXME: verify this
83pub(crate) unsafe extern "C" fn converse<C: Conversation>(
84    num_msg: c_int,
85    msg: *mut *const PamMessage,
86    out_resp: *mut *mut PamResponse,
87    appdata_ptr: *mut c_void,
88) -> c_int {
89    // allocate space for responses
90    let resp =
91        calloc(num_msg as usize, mem::size_of::<PamResponse>() as size_t) as *mut PamResponse;
92    if resp.is_null() {
93        return PamReturnCode::Buf_Err as c_int;
94    }
95
96    let handler = &mut *(appdata_ptr as *mut C);
97
98    let mut result: PamReturnCode = PamReturnCode::Success;
99    for i in 0..num_msg as isize {
100        // get indexed values
101        // FIXME: check this
102        let m: &mut PamMessage = &mut *(*(msg.offset(i)) as *mut PamMessage);
103        let r: &mut PamResponse = &mut *(resp.offset(i));
104
105        let msg = CStr::from_ptr(m.msg);
106        // match on msg_style
107        match PamMessageStyle::from(m.msg_style) {
108            PamMessageStyle::Prompt_Echo_On => {
109                if let Ok(handler_response) = handler.prompt_echo(msg) {
110                    r.resp = strdup(handler_response.as_ptr());
111                } else {
112                    result = PamReturnCode::Conv_Err;
113                }
114            }
115            PamMessageStyle::Prompt_Echo_Off => {
116                if let Ok(handler_response) = handler.prompt_blind(msg) {
117                    r.resp = strdup(handler_response.as_ptr());
118                } else {
119                    result = PamReturnCode::Conv_Err;
120                }
121            }
122            PamMessageStyle::Text_Info => {
123                handler.info(msg);
124            }
125            PamMessageStyle::Error_Msg => {
126                handler.error(msg);
127                result = PamReturnCode::Conv_Err;
128            }
129        }
130        if result != PamReturnCode::Success {
131            break;
132        }
133    }
134
135    // free allocated memory if an error occured
136    if result != PamReturnCode::Success {
137        free(resp as *mut c_void);
138    } else {
139        *out_resp = resp;
140    }
141
142    result as c_int
143}