pam_client/
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_client::{Context, Flag};
25 * use pam_client::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_client::{Context, Flag};
65 * use pam_client::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_client::{Context, Flag};
85 * use pam_client::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 enum_repr::EnumRepr;
140use 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))]
159	pub struct Flag: c_int {
160		/// Don't generate any messages
161		const SILENT = PAM_SILENT as c_int;
162		/// Fail with `AUTH_ERROR` if the user has a null authentication token.
163		const DISALLOW_NULL_AUTHTOK = PAM_DISALLOW_NULL_AUTHTOK as c_int;
164		/// Only update passwords that have aged.
165		const CHANGE_EXPIRED_AUTHTOK = PAM_CHANGE_EXPIRED_AUTHTOK as c_int;
166		/// Set user credentials.
167		#[doc(hidden)]
168		const ESTABLISH_CRED = PAM_ESTABLISH_CRED as c_int;
169		/// Delete user credentials.
170		#[doc(hidden)]
171		const DELETE_CRED = PAM_DELETE_CRED as c_int;
172		/// Reinitialize user credentials.
173		#[doc(hidden)]
174		const REINITIALIZE_CRED = PAM_REINITIALIZE_CRED as c_int;
175		/// Extend lifetime of user credentials.
176		#[doc(hidden)]
177		const REFRESH_CRED = PAM_REFRESH_CRED as c_int;
178	}
179}
180
181#[allow(clippy::upper_case_acronyms)]
182impl Flag {
183	/// No flags; use default behaviour.
184	pub const NONE: Flag = Flag { bits: 0 };
185}
186
187#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
188#[EnumRepr(type = "c_int")]
189#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
190#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
191/// PAM error codes
192pub enum ErrorCode {
193	OPEN_ERR = PAM_OPEN_ERR as c_int,
194	SYMBOL_ERR = PAM_SYMBOL_ERR as c_int,
195	SERVICE_ERR = PAM_SERVICE_ERR as c_int,
196	SYSTEM_ERR = PAM_SYSTEM_ERR as c_int,
197	BUF_ERR = PAM_BUF_ERR as c_int,
198	PERM_DENIED = PAM_PERM_DENIED as c_int,
199	AUTH_ERR = PAM_AUTH_ERR as c_int,
200	CRED_INSUFFICIENT = PAM_CRED_INSUFFICIENT as c_int,
201	AUTHINFO_UNAVAIL = PAM_AUTHINFO_UNAVAIL as c_int,
202	USER_UNKNOWN = PAM_USER_UNKNOWN as c_int,
203	MAXTRIES = PAM_MAXTRIES as c_int,
204	NEW_AUTHTOK_REQD = PAM_NEW_AUTHTOK_REQD as c_int,
205	ACCT_EXPIRED = PAM_ACCT_EXPIRED as c_int,
206	SESSION_ERR = PAM_SESSION_ERR as c_int,
207	CRED_UNAVAIL = PAM_CRED_UNAVAIL as c_int,
208	CRED_EXPIRED = PAM_CRED_EXPIRED as c_int,
209	CRED_ERR = PAM_CRED_ERR as c_int,
210	CONV_ERR = PAM_CONV_ERR as c_int,
211	AUTHTOK_ERR = PAM_AUTHTOK_ERR as c_int,
212	AUTHTOK_RECOVERY_ERR = PAM_AUTHTOK_RECOVERY_ERR as c_int,
213	AUTHTOK_LOCK_BUSY = PAM_AUTHTOK_LOCK_BUSY as c_int,
214	AUTHTOK_DISABLE_AGING = PAM_AUTHTOK_DISABLE_AGING as c_int,
215	ABORT = PAM_ABORT as c_int,
216	AUTHTOK_EXPIRED = PAM_AUTHTOK_EXPIRED as c_int,
217	MODULE_UNKNOWN = PAM_MODULE_UNKNOWN as c_int,
218	BAD_ITEM = PAM_BAD_ITEM as c_int,
219	CONV_AGAIN = PAM_CONV_AGAIN as c_int,
220	INCOMPLETE = PAM_INCOMPLETE as c_int,
221}
222
223/// Type alias for the result of most PAM methods.
224pub type Result<T> = std::result::Result<T, Error>;
225/// Type alias for the result of PAM methods that pass back a consumed struct
226/// on error.
227pub type ExtResult<T, P> = std::result::Result<T, ErrorWith<P>>;
228
229const PAM_SUCCESS: c_int = pam_sys::PAM_SUCCESS as c_int;