Skip to main content

pam_client2/
lib.rs

1/*!
2 * Client application interface for Pluggable Authentication Modules (PAM)
3 *
4 * PAM is the user authentication system used in many Unix-like operating
5 * systems (Linux, NetBSD, MacOS, Solaris, etc.) and handles authentication,
6 * account management, session management and passwort changing via pluggable
7 * modules. This allows programs (such as `login(1)` and `su(1)`) to
8 * authenticate users e.g. in a Kerberos domain as well as locally in
9 * `/etc/passwd`.
10 *
11 * This library provides a safe API to the application-faced parts of PAM,
12 * covering multiple use-cases with a high grade of flexibility.
13 *
14 * It is currently only tested on Linux, but should also support OpenPAM-based
15 * platforms like NetBSD. Solaris should also be supported, but the support
16 * is untested.
17 *
18 * # Examples
19 *
20 * Sample workflow for command line interaction like `su -c`, running a
21 * single program as the user after they successfully authenticated:
22 *
23 * ```no_run
24 * use pam_client2::{Context, Flag};
25 * use pam_client2::conv_cli::Conversation; // CLI implementation
26 * use std::process::Command;
27 * use std::os::unix::process::CommandExt;
28 *
29 * let mut context = Context::new(
30 *    "my-service",       // Service name, decides which policy is used (see `/etc/pam.d`)
31 *    None,               // Optional preset user name
32 *    Conversation::new() // Handler for user interaction
33 * ).expect("Failed to initialize PAM context");
34 *
35 * // Optionally set some settings
36 * context.set_user_prompt(Some("Who art thou? "));
37 *
38 * // Authenticate the user (ask for password, 2nd-factor token, fingerprint, etc.)
39 * context.authenticate(Flag::NONE).expect("Authentication failed");
40 *
41 * // Validate the account (is not locked, expired, etc.)
42 * context.acct_mgmt(Flag::NONE).expect("Account validation failed");
43 *
44 * // Get resulting user name and map to a user id
45 * let username = context.user();
46 * let uid = 65535; // Left as an exercise to the reader
47 *
48 * // Open session and initialize credentials
49 * let mut session = context.open_session(Flag::NONE).expect("Session opening failed");
50 *
51 * // Run a process in the PAM environment
52 * let result = Command::new("/usr/bin/some_program")
53 *                      .env_clear()
54 *                      .envs(session.envlist().iter_tuples())
55 *                      .uid(uid)
56 *                   // .gid(...)
57 *                      .status();
58 *
59 * // The session is automatically closed when it goes out of scope.
60 * ```
61 *
62 * Sample workflow for non-interactive authentication
63 * ```no_run
64 * use pam_client2::{Context, Flag};
65 * use pam_client2::conv_mock::Conversation; // Non-interactive implementation
66 *
67 * let mut context = Context::new(
68 *    "my-service",  // Service name
69 *    None,
70 *    Conversation::with_credentials("username", "password")
71 * ).expect("Failed to initialize PAM context");
72 *
73 * // Authenticate the user
74 * context.authenticate(Flag::NONE).expect("Authentication failed");
75 *
76 * // Validate the account
77 * context.acct_mgmt(Flag::NONE).expect("Account validation failed");
78 *
79 * // ...
80 * ```
81 *
82 * Sample workflow for session management without PAM authentication:
83 * ```no_run
84 * use pam_client2::{Context, Flag};
85 * use pam_client2::conv_null::Conversation; // Null implementation
86 *
87 * let mut context = Context::new(
88 *    "my-service",     // Service name
89 *    Some("username"), // Preset username
90 *    Conversation::new()
91 * ).expect("Failed to initialize PAM context");
92 *
93 * // We already authenticated the user by other means (e.g. SSH key) so we
94 * // skip `context.authenticate()`.
95 *
96 * // Validate the account
97 * context.acct_mgmt(Flag::NONE).expect("Account validation failed");
98 *
99 * // Open session and initialize credentials
100 * let mut session = context.open_session(Flag::NONE).expect("Session opening failed");
101 *
102 * // ...
103 *
104 * // The session is automatically closed when it goes out of scope.
105 * ```
106 */
107
108/***********************************************************************
109 * (c) 2021-2022 Christoph Grenz <christophg+gitorious @ grenz-bonn.de>*
110 *                                                                     *
111 * This Source Code Form is subject to the terms of the Mozilla Public *
112 * License, v. 2.0. If a copy of the MPL was not distributed with this *
113 * file, You can obtain one at http://mozilla.org/MPL/2.0/.            *
114 ***********************************************************************/
115
116mod c_box;
117mod context;
118#[cfg(feature = "cli")]
119pub mod conv_cli;
120pub mod conv_mock;
121pub mod conv_null;
122mod conversation;
123pub mod env_list;
124mod error;
125mod ffi;
126mod resp_buf;
127mod session;
128
129#[macro_use]
130extern crate bitflags;
131use libc::{c_char, c_int};
132use std::ffi::CStr;
133
134pub use context::Context;
135pub use conversation::ConversationHandler;
136pub use error::{Error, ErrorWith};
137pub use session::{Session, SessionToken};
138
139use pam_sys::*;
140pub extern crate pam_sys2 as pam_sys;
141
142fn char_ptr_to_str<'a>(ptr: *const c_char) -> Option<&'a str> {
143	if ptr.is_null() {
144		None
145	} else {
146		let cstr = unsafe { CStr::from_ptr(ptr) };
147		match cstr.to_str() {
148			Err(_) => None,
149			Ok(s) => Some(s),
150		}
151	}
152}
153
154bitflags! {
155	/// Flags for most PAM functions
156	#[allow(clippy::upper_case_acronyms)]
157	#[repr(transparent)]
158	#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
159	#[derive(Copy, Clone)]
160	pub struct Flag: c_int {
161		/// Don't generate any messages
162		const SILENT = PAM_SILENT as c_int;
163		/// Fail with `AUTH_ERROR` if the user has a null authentication token.
164		const DISALLOW_NULL_AUTHTOK = PAM_DISALLOW_NULL_AUTHTOK as c_int;
165		/// Only update passwords that have aged.
166		const CHANGE_EXPIRED_AUTHTOK = PAM_CHANGE_EXPIRED_AUTHTOK as c_int;
167		/// Set user credentials.
168		#[doc(hidden)]
169		const ESTABLISH_CRED = PAM_ESTABLISH_CRED as c_int;
170		/// Delete user credentials.
171		#[doc(hidden)]
172		const DELETE_CRED = PAM_DELETE_CRED as c_int;
173		/// Reinitialize user credentials.
174		#[doc(hidden)]
175		const REINITIALIZE_CRED = PAM_REINITIALIZE_CRED as c_int;
176		/// Extend lifetime of user credentials.
177		#[doc(hidden)]
178		const REFRESH_CRED = PAM_REFRESH_CRED as c_int;
179	}
180}
181
182#[allow(clippy::upper_case_acronyms)]
183impl Flag {
184	/// No flags; use default behaviour.
185	pub const NONE: Flag = Flag::empty();
186}
187
188#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
189#[repr(isize)]
190#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
191#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
192/// PAM error codes
193pub enum ErrorCode {
194	OPEN_ERR = PAM_OPEN_ERR as isize,
195	SYMBOL_ERR = PAM_SYMBOL_ERR as isize,
196	SERVICE_ERR = PAM_SERVICE_ERR as isize,
197	SYSTEM_ERR = PAM_SYSTEM_ERR as isize,
198	BUF_ERR = PAM_BUF_ERR as isize,
199	PERM_DENIED = PAM_PERM_DENIED as isize,
200	AUTH_ERR = PAM_AUTH_ERR as isize,
201	CRED_INSUFFICIENT = PAM_CRED_INSUFFICIENT as isize,
202	AUTHINFO_UNAVAIL = PAM_AUTHINFO_UNAVAIL as isize,
203	USER_UNKNOWN = PAM_USER_UNKNOWN as isize,
204	MAXTRIES = PAM_MAXTRIES as isize,
205	NEW_AUTHTOK_REQD = PAM_NEW_AUTHTOK_REQD as isize,
206	ACCT_EXPIRED = PAM_ACCT_EXPIRED as isize,
207	SESSION_ERR = PAM_SESSION_ERR as isize,
208	CRED_UNAVAIL = PAM_CRED_UNAVAIL as isize,
209	CRED_EXPIRED = PAM_CRED_EXPIRED as isize,
210	CRED_ERR = PAM_CRED_ERR as isize,
211	CONV_ERR = PAM_CONV_ERR as isize,
212	AUTHTOK_ERR = PAM_AUTHTOK_ERR as isize,
213	AUTHTOK_RECOVERY_ERR = PAM_AUTHTOK_RECOVERY_ERR as isize,
214	AUTHTOK_LOCK_BUSY = PAM_AUTHTOK_LOCK_BUSY as isize,
215	AUTHTOK_DISABLE_AGING = PAM_AUTHTOK_DISABLE_AGING as isize,
216	ABORT = PAM_ABORT as isize,
217	AUTHTOK_EXPIRED = PAM_AUTHTOK_EXPIRED as isize,
218	MODULE_UNKNOWN = PAM_MODULE_UNKNOWN as isize,
219	BAD_ITEM = PAM_BAD_ITEM as isize,
220	CONV_AGAIN = PAM_CONV_AGAIN as isize,
221	INCOMPLETE = PAM_INCOMPLETE as isize,
222}
223
224impl ErrorCode {
225    pub fn repr(&self) -> c_int {
226        match self {
227            ErrorCode::OPEN_ERR => PAM_OPEN_ERR as c_int,
228            ErrorCode::SYMBOL_ERR => PAM_SYMBOL_ERR as c_int,
229            ErrorCode::SERVICE_ERR => PAM_SERVICE_ERR as c_int,
230            ErrorCode::SYSTEM_ERR => PAM_SYSTEM_ERR as c_int,
231            ErrorCode::BUF_ERR => PAM_BUF_ERR as c_int,
232            ErrorCode::PERM_DENIED => PAM_PERM_DENIED as c_int,
233            ErrorCode::AUTH_ERR => PAM_AUTH_ERR as c_int,
234            ErrorCode::CRED_INSUFFICIENT => PAM_CRED_INSUFFICIENT as c_int,
235            ErrorCode::AUTHINFO_UNAVAIL => PAM_AUTHINFO_UNAVAIL as c_int,
236            ErrorCode::USER_UNKNOWN => PAM_USER_UNKNOWN as c_int,
237            ErrorCode::MAXTRIES => PAM_MAXTRIES as c_int,
238            ErrorCode::NEW_AUTHTOK_REQD => PAM_NEW_AUTHTOK_REQD as c_int,
239            ErrorCode::ACCT_EXPIRED => PAM_ACCT_EXPIRED as c_int,
240            ErrorCode::SESSION_ERR => PAM_SESSION_ERR as c_int,
241            ErrorCode::CRED_UNAVAIL => PAM_CRED_UNAVAIL as c_int,
242            ErrorCode::CRED_EXPIRED => PAM_CRED_EXPIRED as c_int,
243            ErrorCode::CRED_ERR => PAM_CRED_ERR as c_int,
244            ErrorCode::CONV_ERR => PAM_CONV_ERR as c_int,
245            ErrorCode::AUTHTOK_ERR => PAM_AUTHTOK_ERR as c_int,
246            ErrorCode::AUTHTOK_RECOVERY_ERR => PAM_AUTHTOK_RECOVERY_ERR as c_int,
247            ErrorCode::AUTHTOK_LOCK_BUSY => PAM_AUTHTOK_LOCK_BUSY as c_int,
248            ErrorCode::AUTHTOK_DISABLE_AGING => {
249                PAM_AUTHTOK_DISABLE_AGING as c_int
250            }
251            ErrorCode::ABORT => PAM_ABORT as c_int,
252            ErrorCode::AUTHTOK_EXPIRED => PAM_AUTHTOK_EXPIRED as c_int,
253            ErrorCode::MODULE_UNKNOWN => PAM_MODULE_UNKNOWN as c_int,
254            ErrorCode::BAD_ITEM => PAM_BAD_ITEM as c_int,
255            ErrorCode::CONV_AGAIN => PAM_CONV_AGAIN as c_int,
256            ErrorCode::INCOMPLETE => PAM_INCOMPLETE as c_int,
257        }
258    }
259    pub fn from_repr(x: c_int) -> Option<ErrorCode> {
260		match x {
261			PAM_OPEN_ERR => Some(ErrorCode::OPEN_ERR),
262			PAM_SYMBOL_ERR => Some(ErrorCode::SYMBOL_ERR),
263			PAM_SERVICE_ERR => Some(ErrorCode::SERVICE_ERR),
264			PAM_SYSTEM_ERR => Some(ErrorCode::SYSTEM_ERR),
265			PAM_BUF_ERR => Some(ErrorCode::BUF_ERR),
266			PAM_PERM_DENIED => Some(ErrorCode::PERM_DENIED),
267			PAM_AUTH_ERR => Some(ErrorCode::AUTH_ERR),
268			PAM_CRED_INSUFFICIENT => Some(ErrorCode::CRED_INSUFFICIENT),
269			PAM_AUTHINFO_UNAVAIL => Some(ErrorCode::AUTHINFO_UNAVAIL),
270			PAM_USER_UNKNOWN => Some(ErrorCode::USER_UNKNOWN),
271			PAM_MAXTRIES => Some(ErrorCode::MAXTRIES),
272			PAM_NEW_AUTHTOK_REQD => Some(ErrorCode::NEW_AUTHTOK_REQD),
273			PAM_ACCT_EXPIRED => Some(ErrorCode::ACCT_EXPIRED),
274			PAM_SESSION_ERR => Some(ErrorCode::SESSION_ERR),
275			PAM_CRED_UNAVAIL => Some(ErrorCode::CRED_UNAVAIL),
276			PAM_CRED_EXPIRED => Some(ErrorCode::CRED_EXPIRED),
277			PAM_CRED_ERR => Some(ErrorCode::CRED_ERR),
278			PAM_CONV_ERR => Some(ErrorCode::CONV_ERR),
279			PAM_AUTHTOK_ERR => Some(ErrorCode::AUTHTOK_ERR),
280			PAM_AUTHTOK_RECOVERY_ERR => Some(ErrorCode::AUTHTOK_RECOVERY_ERR),
281			PAM_AUTHTOK_LOCK_BUSY => Some(ErrorCode::AUTHTOK_LOCK_BUSY),
282			PAM_AUTHTOK_DISABLE_AGING => Some(ErrorCode::AUTHTOK_DISABLE_AGING),
283			PAM_ABORT => Some(ErrorCode::ABORT),
284			PAM_AUTHTOK_EXPIRED => Some(ErrorCode::AUTHTOK_EXPIRED),
285			PAM_MODULE_UNKNOWN => Some(ErrorCode::MODULE_UNKNOWN),
286			PAM_BAD_ITEM => Some(ErrorCode::BAD_ITEM),
287			PAM_CONV_AGAIN => Some(ErrorCode::CONV_AGAIN),
288			PAM_INCOMPLETE => Some(ErrorCode::INCOMPLETE),
289			_ => None,
290		}
291    }
292}
293
294/// Type alias for the result of most PAM methods.
295pub type Result<T> = std::result::Result<T, Error>;
296/// Type alias for the result of PAM methods that pass back a consumed struct
297/// on error.
298pub type ExtResult<T, P> = std::result::Result<T, ErrorWith<P>>;
299
300const PAM_SUCCESS: c_int = pam_sys::PAM_SUCCESS as c_int;