pam_client2/
conversation.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Conversation trait definition module

/***********************************************************************
 * (c) 2021 Christoph Grenz <christophg+gitorious @ grenz-bonn.de>     *
 *                                                                     *
 * This Source Code Form is subject to the terms of the Mozilla Public *
 * License, v. 2.0. If a copy of the MPL was not distributed with this *
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.            *
 ***********************************************************************/

#![forbid(unsafe_code)]

use crate::error::ErrorCode;
use std::ffi::{CStr, CString};
use std::result::Result;

/// Trait for PAM conversation functions
///
/// Implement this for custom behaviour when a PAM module asks for usernames,
/// passwords, etc. or wants to show a message to the user
#[rustversion::attr(since(1.48), doc(alias = "pam_conv"))]
pub trait ConversationHandler {
	/// Called by [`Context`][`crate::Context`] directly after taking ownership
	/// of the handler.
	///
	/// May be called multiple times if
	/// [`Context::replace_conversation()`][`crate::Context::replace_conversation`]
	/// is used. In this case it is called each time a context takes ownership
	/// and passed the current target username of that context (if any) as the
	/// argument.
	///
	/// The default implementation does nothing.
	fn init(&mut self, _default_user: Option<&str>) {}

	/// Obtains a string whilst echoing text (e.g. username)
	///
	/// # Errors
	/// You should return one of the following error codes on failure.
	/// - [`ErrorCode::CONV_ERR`]: Conversation failure.
	/// - [`ErrorCode::BUF_ERR`]: Memory allocation error.
	/// - [`ErrorCode::CONV_AGAIN`]: no result yet, the PAM library should
	///   pass [`ErrorCode::INCOMPLETE`] to the application and let it
	///   try again later.
	fn prompt_echo_on(&mut self, prompt: &CStr) -> Result<CString, ErrorCode>;

	/// Obtains a string without echoing any text (e.g. password)
	///
	/// # Errors
	/// You should return one of the following error codes on failure.
	/// - [`ErrorCode::CONV_ERR`]: Conversation failure.
	/// - [`ErrorCode::BUF_ERR`]: Memory allocation error.
	/// - [`ErrorCode::CONV_AGAIN`]: no result yet, the PAM library should
	///   pass [`ErrorCode::INCOMPLETE`] to the application and let it
	///   try again later.
	fn prompt_echo_off(&mut self, prompt: &CStr) -> Result<CString, ErrorCode>;

	/// Displays some text.
	fn text_info(&mut self, msg: &CStr);

	/// Displays an error message.
	fn error_msg(&mut self, msg: &CStr);

	/// Obtains a yes/no answer (Linux specific).
	///
	/// The default implementation calls `prompt_echo_on` and maps any answer
	/// starting with 'y' or 'j' to "yes" and everything else to "no".
	///
	/// # Errors
	/// You should return one of the following error codes on failure.
	/// - [`ErrorCode::CONV_ERR`]: Conversation failure.
	/// - [`ErrorCode::BUF_ERR`]: Memory allocation error.
	/// - [`ErrorCode::CONV_AGAIN`]: no result yet, the PAM library should
	///   pass [`ErrorCode::INCOMPLETE`] to the application and let it
	///   try again later.
	fn radio_prompt(&mut self, prompt: &CStr) -> Result<bool, ErrorCode> {
		let prompt = [prompt.to_bytes(), b" [y/N]\0"].concat();

		self.prompt_echo_on(CStr::from_bytes_with_nul(&prompt).unwrap())
			.map(|s| matches!(s.as_bytes_with_nul()[0], b'Y' | b'y' | b'j' | b'J'))
	}

	/// Exchanges binary data (Linux specific, experimental).
	///
	/// The default implementation returns a conversation error.
	///
	/// # Errors
	/// You should return one of the following error codes on failure.
	/// - [`ErrorCode::CONV_ERR`]: Conversation failure.
	/// - [`ErrorCode::BUF_ERR`]: Memory allocation error.
	/// - [`ErrorCode::CONV_AGAIN`]: no result yet, the PAM library should
	///   pass [`ErrorCode::INCOMPLETE`] to the application and let it
	///   try again later.
	fn binary_prompt(&mut self, _type: u8, _data: &[u8]) -> Result<(u8, Vec<u8>), ErrorCode> {
		Err(ErrorCode::CONV_ERR)
	}
}

macro_rules! impl_for_wrapper {
	($type:ty) => {
		impl_for_wrapper!($type, <>);
	};
	($type:ty, $($params:tt)*) => {
		impl $($params)* ConversationHandler for $type {
			#[inline]
			fn init(&mut self, default_user: Option<&str>) {
				(**self).init(default_user)
			}

			#[inline]
			fn prompt_echo_on(&mut self, prompt: &CStr) -> Result<CString, ErrorCode> {
				(**self).prompt_echo_on(prompt)
			}

			#[inline]
			fn prompt_echo_off(&mut self, prompt: &CStr) -> Result<CString, ErrorCode> {
				(**self).prompt_echo_off(prompt)
			}

			#[inline]
			fn text_info(&mut self, msg: &CStr) {
				(**self).text_info(msg)
			}

			#[inline]
			fn error_msg(&mut self, msg: &CStr) {
				(**self).error_msg(msg)
			}

			#[inline]
			fn radio_prompt(&mut self, prompt: &CStr) -> Result<bool, ErrorCode> {
				(**self).radio_prompt(prompt)
			}

			#[inline]
			fn binary_prompt(
				&mut self,
				type_: u8,
				data: &[u8],
			) -> Result<(u8, Vec<u8>), ErrorCode> {
				(**self).binary_prompt(type_, data)
			}
		}
	};
}
impl_for_wrapper!(&'a mut T, <'a, T: ConversationHandler + ?Sized>);
impl_for_wrapper!(Box<T>, <T: ConversationHandler + ?Sized>);