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;