Skip to main content

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