pam_client/
session.rs

1//! PAM sessions and related structs
2
3/***********************************************************************
4 * (c) 2021-2022 Christoph Grenz <christophg+gitorious @ grenz-bonn.de>*
5 *                                                                     *
6 * This Source Code Form is subject to the terms of the Mozilla Public *
7 * License, v. 2.0. If a copy of the MPL was not distributed with this *
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.            *
9 ***********************************************************************/
10
11use crate::env_list::EnvList;
12use crate::Context;
13use crate::ConversationHandler;
14use crate::{ExtResult, Flag, Result};
15
16use pam_sys::{pam_close_session, pam_setcred};
17use std::ffi::OsStr;
18use std::mem::drop;
19
20/// Token type to resume RAII handling of a session that was released with [`Session::leak()`].
21///
22/// The representation may not yet be stable, so don't rely on it.
23#[derive(Debug, Clone, Copy)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[must_use]
26pub enum SessionToken {
27	FullSession,
28	PseudoSession,
29}
30
31/// An active PAM session or pseudo session
32#[must_use]
33pub struct Session<'a, ConvT>
34where
35	ConvT: ConversationHandler,
36{
37	context: &'a mut Context<ConvT>,
38	session_active: bool,
39	credentials_active: bool,
40}
41
42impl<'a, ConvT> Session<'a, ConvT>
43where
44	ConvT: ConversationHandler,
45{
46	/// Constructs a `Session` object for a PAM context.
47	pub(crate) fn new(context: &'a mut Context<ConvT>, real: bool) -> Session<'a, ConvT> {
48		Self {
49			context,
50			session_active: real,
51			credentials_active: true,
52		}
53	}
54
55	/// Extends the lifetime of existing credentials.
56	///
57	/// Might be called periodically for long running sessions to
58	/// keep e.g. Kerberos tokens alive.
59	///
60	/// Relevant `flags` are [`Flag::NONE`] and [`Flag::SILENT`].
61	///
62	/// # Errors
63	/// Expected error codes include:
64	/// - `ReturnCode::BUF_ERR`: Memory allocation error
65	/// - `ReturnCode::CRED_ERR`: Setting credentials failed
66	/// - `ReturnCode::CRED_EXPIRED`: Credentials are expired
67	/// - `ReturnCode::CRED_UNAVAIL`: Failed to retrieve credentials
68	/// - `ReturnCode::SYSTEM_ERR`: Other system error
69	/// - `ReturnCode::USER_UNKNOWN`: User not known
70	pub fn refresh_credentials(&mut self, flags: Flag) -> Result<()> {
71		self.context.wrap_pam_return(unsafe {
72			pam_setcred(
73				self.context.handle().into(),
74				(Flag::REFRESH_CRED | flags).bits(),
75			)
76		})
77	}
78
79	/// Fully reinitializes the user's credentials.
80	///
81	/// Relevant `flags` are [`Flag::NONE`] and [`Flag::SILENT`].
82	///
83	/// See [`Context::reinitialize_credentials()`] for more information.
84	pub fn reinitialize_credentials(&mut self, flags: Flag) -> Result<()> {
85		self.context.wrap_pam_return(unsafe {
86			pam_setcred(
87				self.context.handle().into(),
88				(Flag::REINITIALIZE_CRED | flags).bits(),
89			)
90		})
91	}
92
93	/// Converts the session into a [`SessionToken`] without closing it.
94	///
95	/// The returned token can be used to resume handling the
96	/// session with [`Context::unleak_session()`].
97	///
98	/// Please note, that if the session isn't closed eventually and the
99	/// established credentials aren't deleted, security problems might
100	/// occur.
101	///
102	/// Depending on the platform it may be possible to close the session
103	/// from another context than the one that started the session. But as
104	/// this behaviour cannot be safely relied upon, it is recommended to
105	/// close the session within the same PAM context.
106	pub fn leak(mut self) -> SessionToken {
107		let result = if self.session_active {
108			SessionToken::FullSession
109		} else {
110			SessionToken::PseudoSession
111		};
112		self.session_active = false;
113		self.credentials_active = false;
114		result
115	}
116
117	/// Returns the value of a PAM environment variable.
118	///
119	/// See [`Context::getenv()`].
120	#[must_use]
121	#[rustversion::attr(since(1.48), doc(alias = "pam_getenv"))]
122	pub fn getenv(&self, name: impl AsRef<OsStr>) -> Option<&str> {
123		self.context.getenv(name)
124	}
125
126	/// Sets or unsets a PAM environment variable.
127	///
128	/// See [`Context::putenv()`].
129	#[rustversion::attr(since(1.48), doc(alias = "pam_putenv"))]
130	pub fn putenv(&mut self, name_value: impl AsRef<OsStr>) -> Result<()> {
131		self.context.putenv(name_value)
132	}
133
134	/// Returns a copy of the PAM environment in this context.
135	///
136	/// See [`Context::envlist()`].
137	#[must_use]
138	#[rustversion::attr(since(1.48), doc(alias = "pam_getenvlist"))]
139	pub fn envlist(&self) -> EnvList {
140		self.context.envlist()
141	}
142
143	/// Manually closes the session
144	///
145	/// Closes the PAM session and deletes credentials established when opening
146	/// the session. Session closing happens automatically when dropping
147	/// the session, so this is not strictly required.
148	///
149	/// Please note that the application must usually have the same privileges
150	/// to close as it had to open the session (e.g. have EUID 0).
151	///
152	/// Relevant `flags` are [`Flag::NONE`] and [`Flag::SILENT`]
153	///
154	/// # Errors
155	///
156	/// Expected error codes include:
157	/// - `ReturnCode::ABORT`: Generic failure
158	/// - `ReturnCode::BUF_ERR`: Memory allocation error
159	/// - `ReturnCode::SESSION_ERR`: Generic session failure
160	/// - `ReturnCode::CRED_ERR`: Deleting credentials failed
161	/// - `ReturnCode::SYSTEM_ERR`: Other system error
162	///
163	/// The ownership of `self` is passed back in the error payload.
164	/// On drop the session will once again try to close itself.
165	#[rustversion::attr(since(1.48), doc(alias = "pam_close_session"))]
166	pub fn close(mut self, flags: Flag) -> ExtResult<(), Self> {
167		let handle = self.context.handle().as_ptr();
168		if self.session_active {
169			let status = unsafe { pam_close_session(handle, flags.bits()) };
170			if let Err(e) = self.context.wrap_pam_return(status) {
171				return Err(e.into_with_payload(self));
172			}
173			self.session_active = false;
174		}
175		if self.credentials_active {
176			let status = unsafe { pam_setcred(handle, (Flag::DELETE_CRED | flags).bits()) };
177			if let Err(e) = self.context.wrap_pam_return(status) {
178				return Err(e.into_with_payload(self));
179			}
180			self.credentials_active = false;
181		}
182		Ok(())
183	}
184}
185
186/// Destructor ending the PAM session and deleting established credentials
187impl<'a, ConvT> Drop for Session<'a, ConvT>
188where
189	ConvT: ConversationHandler,
190{
191	fn drop(&mut self) {
192		let handle = self.context.handle().as_ptr();
193		if self.session_active {
194			let status = unsafe { pam_close_session(handle, Flag::NONE.bits()) };
195			self.session_active = false;
196			drop(self.context.wrap_pam_return(status));
197		}
198		if self.credentials_active {
199			let status = unsafe { pam_setcred(handle, (Flag::DELETE_CRED | Flag::SILENT).bits()) };
200			self.credentials_active = false;
201			drop(self.context.wrap_pam_return(status));
202		}
203	}
204}
205
206#[cfg(test)]
207mod tests {
208	use super::*;
209
210	#[test]
211	fn test_token() {
212		let token = SessionToken::FullSession;
213
214		let mut context = Context::new(
215			"test",
216			Some("user"),
217			crate::conv_null::Conversation::default(),
218		)
219		.unwrap();
220		let mut session = context.unleak_session(token);
221		let _ = session.putenv("TEST=1");
222		let _ = session.getenv("TEST");
223		let _ = session.envlist();
224		let _ = session.leak();
225
226		let session = context.unleak_session(SessionToken::PseudoSession);
227		let _ = session.leak();
228	}
229}