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