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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
//! PAM sessions and related structs

/***********************************************************************
 * (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/.            *
 ***********************************************************************/

use crate::Context;
use crate::ConversationHandler;
use crate::env_list::EnvList;
use crate::{Result, ExtResult, Flag};

use pam_sys::wrapped::{setcred, close_session};

/// Token type to resume RAII handling of a session that was released with [`Session::leak()`].
///
/// The representation may not yet be stable, so don't rely on it.
#[derive(Debug,Clone,Copy)]
#[must_use]
pub enum SessionToken {
	FullSession,
	PseudoSession
}

/// An active PAM session or pseudo session
#[must_use]
pub struct Session<'a, ConvT> where ConvT: ConversationHandler {
	context: &'a mut Context<ConvT>,
	session_active: bool,
	credentials_active: bool
}

impl<'a, ConvT> Session<'a, ConvT> where ConvT: ConversationHandler {
	/// Constructs a `Session` object for a PAM context.
	pub(crate) fn new(context: &'a mut Context<ConvT>, real: bool) -> Session<'a, ConvT> {
		Self {
			context,
			session_active: real,
			credentials_active: true
		}
	}

	/// Extends the lifetime of existing credentials.
	///
	/// Might be called periodically for long running sessions to
	/// keep e.g. Kerberos tokens alive.
	///
	/// Because of limitations in [`pam_sys`] the flags are currently ignored.
	/// Use [`Flag::NONE`] or [`Flag::SILENT`].
	///
	/// # Errors
	/// Expected error codes include:
	/// - `ReturnCode::BUF_ERR`: Memory allocation error
	/// - `ReturnCode::CRED_ERR`: Setting credentials failed
	/// - `ReturnCode::CRED_EXPIRED`: Credentials are expired
	/// - `ReturnCode::CRED_UNAVAIL`: Failed to retrieve credentials
	/// - `ReturnCode::SYSTEM_ERR`: Other system error
	/// - `ReturnCode::USER_UNKNOWN`: User not known
	pub fn refresh_credentials(&mut self, _flags: Flag) -> Result<()> {
		self.context.wrap_pam_return(setcred(self.context.handle(), Flag::REFRESH_CRED/*|flags*/))
	}

	/// Fully reinitializes the user's credentials.
	///
	/// Because of limitations in [`pam_sys`] the flags are currently ignored.
	/// Use [`Flag::NONE`] or [`Flag::SILENT`].
	///
	/// See [`Context::reinitialize_credentials()`] for more information.
	pub fn reinitialize_credentials(&mut self, _flags: Flag) -> Result<()> {
		self.context.wrap_pam_return(setcred(self.context.handle(), Flag::REINITIALIZE_CRED/*|flags*/))
	}

	/// Converts the session into a [`SessionToken`] without closing it.
	///
	/// The returned token can be used to resume handling the
	/// session with [`Context::unleak_session()`].
	///
	/// Please note, that if the session isn't closed eventually and the
	/// established credentials aren't deleted, security problems might
	/// occur.
	///
	/// Depending on the platform it may be possible to close the session
	/// from another context than the one that started the session. But as
	/// this behaviour cannot be safely relied upon, it is recommended to
	/// close the session within the same PAM context.
	pub fn leak(mut self) -> SessionToken {
		let result = if self.session_active { SessionToken::FullSession } else { SessionToken::PseudoSession };
		self.session_active = false;
		self.credentials_active = false;
		result
	}

	/// Returns the value of a PAM environment variable.
	///
	/// See [`Context::getenv()`].
	#[rustversion::attr(since(1.48), doc(alias = "pam_getenv"))]
	pub fn getenv<'b>(&'b self, name: &str) -> Option<&'b str> {
		self.context.getenv(name)
	}

	/// Sets or unsets a PAM environment variable.
	///
	/// See [`Context::putenv()`].
	#[rustversion::attr(since(1.48), doc(alias = "pam_putenv"))]
	pub fn putenv(&mut self, name_value: &str) -> Result<()> {
		self.context.putenv(name_value)
	}

	/// Returns a copy of the PAM environment in this context.
	///
	/// See [`Context::envlist()`].
	#[rustversion::attr(since(1.48), doc(alias = "pam_getenvlist"))]
	pub fn envlist(&self) -> EnvList {
		self.context.envlist()
	}

	/// Manually closes the session
	///
	/// Closes the PAM session and deletes credentials established when opening
	/// the session. Session closing happens automatically when dropping
	/// the session, so this is not strictly required.
	///
	/// Please note that the application must usually have the same privileges
	/// to close as it had to open the session (e.g. have EUID 0).
	///
	/// Relevant `flags` are [`Flag::NONE`] and [`Flag::SILENT`]
	///
	/// # Errors
	///
	/// Expected error codes include:
	/// - `ReturnCode::ABORT`: Generic failure
	/// - `ReturnCode::BUF_ERR`: Memory allocation error
	/// - `ReturnCode::SESSION_ERR`: Generic session failure
	/// - `ReturnCode::CRED_ERR`: Deleting credentials failed
	/// - `ReturnCode::SYSTEM_ERR`: Other system error
	///
	/// The ownership of `self` is passed back in the error payload.
	/// On drop the session will once again try to close itself.
	#[rustversion::attr(since(1.48), doc(alias = "pam_close_session"))]
	pub fn close(mut self, flags: Flag) -> ExtResult<(), Self> {
		if self.session_active {
			let status = close_session(self.context.handle(), flags);
			if let Err(e) = self.context.wrap_pam_return(status) {
				return Err(e.into_with_payload(self));
			}
			self.session_active = false;
		}
		if self.credentials_active {
			let status = setcred(self.context.handle(), Flag::DELETE_CRED/*|flags*/);
			if let Err(e) = self.context.wrap_pam_return(status) {
				return Err(e.into_with_payload(self));
			}
			self.credentials_active = false;
		}
		Ok(())
	}
}

/// Destructor ending the PAM session and deleting established credentials
impl<'a, ConvT> Drop for Session<'a, ConvT> where ConvT: ConversationHandler {
	fn drop(&mut self) {
		if self.session_active {
			let status = close_session(self.context.handle(), Flag::NONE);
			self.session_active = false;
			let _ = self.context.wrap_pam_return(status);
		}
		if self.credentials_active {
			let status = setcred(self.context.handle(), Flag::DELETE_CRED/*|Flag::SILENT*/);
			self.credentials_active = false;
			let _ = self.context.wrap_pam_return(status);
		}
	}
}