futures = "0.3.30"
serde = "1.0.204"
idmap = { workspace = true }
-libc = "0.2.155"
+libc = { workspace = true }
[build-dependencies]
version = { path = "version" }
[workspace]
members = [
"chelps", "dbg", "idmap",
- "nss", "ntstatus_gen",
+ "nss", "ntstatus_gen", "pam",
"param", "sock", "tdb", "version",
]
ntstatus_gen = { path = "ntstatus_gen" }
tdb = { path = "tdb" }
idmap = { path = "idmap" }
+libc = "0.2.155"
[dependencies]
chelps.workspace = true
dbg.workspace = true
-libc = "0.2.153"
+libc.workspace = true
[build-dependencies]
cc = "1.0.97"
path = "src/lib.rs"
[dependencies]
-libc = "0.2.155"
+libc.workspace = true
libnss = "0.8.0"
ntstatus_gen.workspace = true
param = { workspace = true }
--- /dev/null
+[package]
+name = "pam_himmelblau"
+links = "pam"
+
+edition.workspace = true
+license.workspace = true
+homepage.workspace = true
+version.workspace = true
+
+[lib]
+name = "pam_himmelblau"
+crate-type = [ "cdylib" ]
+path = "src/lib.rs"
+
+[dependencies]
+libc = { workspace = true }
+dbg = { workspace = true }
+sock.workspace = true
+chelps.workspace = true
+param.workspace = true
+
+[build-dependencies]
+pkg-config = "0.3.30"
--- /dev/null
+fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ // ignore errors here since older versions of pam do not ship the pkg-config `pam.pc` file.
+ // Not setting anything here will fall back on just blindly linking with `-lpam`,
+ // which will work on environments with libpam.so, but no pkg-config file.
+ let _ = pkg_config::Config::new()
+ .atleast_version("1.3.0")
+ .probe("pam");
+}
--- /dev/null
+/*
+ MIT License
+
+ Copyright (c) 2015 TOZNY
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+#![deny(warnings)]
+#![warn(unused_extern_crates)]
+#![deny(clippy::todo)]
+#![deny(clippy::unimplemented)]
+// In this file, we do want to panic on these faults.
+// #![deny(clippy::unwrap_used)]
+// #![deny(clippy::expect_used)]
+#![deny(clippy::panic)]
+#![deny(clippy::unreachable)]
+#![deny(clippy::await_holding_lock)]
+#![deny(clippy::needless_pass_by_value)]
+#![deny(clippy::trivially_copy_pass_by_ref)]
+
+#[cfg(target_family = "unix")]
+mod pam;
+
+// pub use needs to be here so it'll compile and export all the things
+#[cfg(target_family = "unix")]
+pub use crate::pam::*;
--- /dev/null
+/*
+ MIT License
+
+ Copyright (c) 2015 TOZNY
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+use libc::{c_int, c_uint};
+
+pub type PamFlag = c_uint;
+pub type PamItemType = c_int;
+pub type PamMessageStyle = c_int;
+pub type AlwaysZero = c_int;
+
+// The Linux-PAM flags
+// see /usr/include/security/_pam_types.h
+pub const _PAM_SILENT: PamFlag = 0x8000;
+pub const _PAM_DISALLOW_NULL_AUTHTOK: PamFlag = 0x0001;
+pub const _PAM_ESTABLISH_CRED: PamFlag = 0x0002;
+pub const _PAM_DELETE_CRED: PamFlag = 0x0004;
+pub const _PAM_REINITIALIZE_CRED: PamFlag = 0x0008;
+pub const _PAM_REFRESH_CRED: PamFlag = 0x0010;
+pub const _PAM_CHANGE_EXPIRED_AUTHTOK: PamFlag = 0x0020;
+
+// The Linux-PAM item types
+// see /usr/include/security/_pam_types.h
+/// The service name
+pub const PAM_SERVICE: PamItemType = 1;
+/// The user name
+pub const PAM_USER: PamItemType = 2;
+/// The tty name
+pub const PAM_TTY: PamItemType = 3;
+/// The remote host name
+pub const PAM_RHOST: PamItemType = 4;
+/// The pam_conv structure
+pub const PAM_CONV: PamItemType = 5;
+/// The authentication token (password)
+pub const PAM_AUTHTOK: PamItemType = 6;
+/// The old authentication token
+pub const PAM_OLDAUTHTOK: PamItemType = 7;
+/// The remote user name
+pub const PAM_RUSER: PamItemType = 8;
+/// the prompt for getting a username
+pub const PAM_USER_PROMPT: PamItemType = 9;
+/* Linux-PAM :extensionsPamItemType = */
+/// app supplied function to override failure delays
+pub const _PAM_FAIL_DELAY: PamItemType = 10;
+/// X :display name
+pub const _PAM_XDISPLAY: PamItemType = 11;
+/// X :server authentication data
+pub const _PAM_XAUTHDATA: PamItemType = 12;
+/// The type for pam_get_authtok
+pub const _PAM_AUTHTOK_TYPE: PamItemType = 13;
+
+// Message styles
+pub const PAM_PROMPT_ECHO_OFF: PamMessageStyle = 1;
+pub const PAM_PROMPT_ECHO_ON: PamMessageStyle = 2;
+pub const PAM_ERROR_MSG: PamMessageStyle = 3;
+pub const PAM_TEXT_INFO: PamMessageStyle = 4;
+/// yes/no/maybe conditionals
+pub const _PAM_RADIO_TYPE: PamMessageStyle = 5;
+pub const _PAM_BINARY_PROMPT: PamMessageStyle = 7;
+
+// The Linux-PAM return values
+// see /usr/include/security/_pam_types.h
+#[allow(non_camel_case_types, dead_code)]
+#[derive(Debug, PartialEq)]
+#[repr(C)]
+pub enum PamResultCode {
+ PAM_SUCCESS = 0,
+ PAM_OPEN_ERR = 1,
+ PAM_SYMBOL_ERR = 2,
+ PAM_SERVICE_ERR = 3,
+ PAM_SYSTEM_ERR = 4,
+ PAM_BUF_ERR = 5,
+ PAM_PERM_DENIED = 6,
+ PAM_AUTH_ERR = 7,
+ PAM_CRED_INSUFFICIENT = 8,
+ PAM_AUTHINFO_UNAVAIL = 9,
+ PAM_USER_UNKNOWN = 10,
+ PAM_MAXTRIES = 11,
+ PAM_NEW_AUTHTOK_REQD = 12,
+ PAM_ACCT_EXPIRED = 13,
+ PAM_SESSION_ERR = 14,
+ PAM_CRED_UNAVAIL = 15,
+ PAM_CRED_EXPIRED = 16,
+ PAM_CRED_ERR = 17,
+ PAM_NO_MODULE_DATA = 18,
+ PAM_CONV_ERR = 19,
+ PAM_AUTHTOK_ERR = 20,
+ PAM_AUTHTOK_RECOVERY_ERR = 21,
+ PAM_AUTHTOK_LOCK_BUSY = 22,
+ PAM_AUTHTOK_DISABLE_AGING = 23,
+ PAM_TRY_AGAIN = 24,
+ PAM_IGNORE = 25,
+ PAM_ABORT = 26,
+ PAM_AUTHTOK_EXPIRED = 27,
+ PAM_MODULE_UNKNOWN = 28,
+ PAM_BAD_ITEM = 29,
+ PAM_CONV_AGAIN = 30,
+ PAM_INCOMPLETE = 31,
+}
--- /dev/null
+/*
+ MIT License
+
+ Copyright (c) 2015 TOZNY
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+use std::ffi::{CStr, CString};
+use std::ptr;
+
+use libc::{c_char, c_int};
+
+use crate::pam::constants::{PamResultCode, *};
+use crate::pam::module::{PamItem, PamResult};
+
+#[allow(missing_copy_implementations)]
+pub enum AppDataPtr {}
+
+#[repr(C)]
+struct PamMessage {
+ msg_style: PamMessageStyle,
+ msg: *const c_char,
+}
+
+#[repr(C)]
+struct PamResponse {
+ resp: *const c_char,
+ resp_retcode: AlwaysZero,
+}
+
+/// `PamConv` acts as a channel for communicating with user.
+///
+/// Communication is mediated by the pam client (the application that invoked
+/// pam). Messages sent will be relayed to the user by the client, and response
+/// will be relayed back.
+#[repr(C)]
+pub struct PamConv {
+ conv: extern "C" fn(
+ num_msg: c_int,
+ pam_message: &&PamMessage,
+ pam_response: &mut *const PamResponse,
+ appdata_ptr: *const AppDataPtr,
+ ) -> PamResultCode,
+ appdata_ptr: *const AppDataPtr,
+}
+
+impl PamConv {
+ /// Sends a message to the pam client.
+ ///
+ /// This will typically result in the user seeing a message or a prompt.
+ /// There are several message styles available:
+ ///
+ /// - PAM_PROMPT_ECHO_OFF
+ /// - PAM_PROMPT_ECHO_ON
+ /// - PAM_ERROR_MSG
+ /// - PAM_TEXT_INFO
+ /// - PAM_RADIO_TYPE
+ /// - PAM_BINARY_PROMPT
+ ///
+ /// Note that the user experience will depend on how the client implements
+ /// these message styles - and not all applications implement all message
+ /// styles.
+ pub fn send(
+ &self,
+ style: PamMessageStyle,
+ msg: &str,
+ ) -> PamResult<Option<String>> {
+ let mut resp_ptr: *const PamResponse = ptr::null();
+ let msg_cstr = CString::new(msg).unwrap();
+ let msg = PamMessage {
+ msg_style: style,
+ msg: msg_cstr.as_ptr(),
+ };
+
+ let ret = (self.conv)(1, &&msg, &mut resp_ptr, self.appdata_ptr);
+
+ if PamResultCode::PAM_SUCCESS == ret {
+ // PamResponse.resp is null for styles that don't return user input like PAM_TEXT_INFO
+ let response = unsafe { (*resp_ptr).resp };
+ if response.is_null() {
+ Ok(None)
+ } else {
+ let bytes = unsafe { CStr::from_ptr(response).to_bytes() };
+ Ok(String::from_utf8(bytes.to_vec()).ok())
+ }
+ } else {
+ Err(ret)
+ }
+ }
+}
+
+impl PamItem for PamConv {
+ fn item_type() -> PamItemType {
+ PAM_CONV
+ }
+}
--- /dev/null
+/*
+ MIT License
+
+ Copyright (c) 2015 TOZNY
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+use crate::pam::constants::{
+ PamItemType, PAM_AUTHTOK, PAM_OLDAUTHTOK, PAM_RHOST, PAM_RUSER,
+ PAM_SERVICE, PAM_TTY, PAM_USER, PAM_USER_PROMPT,
+};
+pub use crate::pam::conv::PamConv;
+use crate::pam::module::PamItem;
+
+#[allow(dead_code)]
+pub struct PamService {}
+
+impl PamItem for PamService {
+ fn item_type() -> PamItemType {
+ PAM_SERVICE
+ }
+}
+
+#[allow(dead_code)]
+pub struct PamUser {}
+
+impl PamItem for PamUser {
+ fn item_type() -> PamItemType {
+ PAM_USER
+ }
+}
+
+#[allow(dead_code)]
+pub struct PamUserPrompt {}
+
+impl PamItem for PamUserPrompt {
+ fn item_type() -> PamItemType {
+ PAM_USER_PROMPT
+ }
+}
+
+#[allow(dead_code)]
+pub struct PamTty {}
+
+impl PamItem for PamTty {
+ fn item_type() -> PamItemType {
+ PAM_TTY
+ }
+}
+
+#[allow(dead_code)]
+pub struct PamRUser {}
+
+impl PamItem for PamRUser {
+ fn item_type() -> PamItemType {
+ PAM_RUSER
+ }
+}
+
+#[allow(dead_code)]
+pub struct PamRHost {}
+
+impl PamItem for PamRHost {
+ fn item_type() -> PamItemType {
+ PAM_RHOST
+ }
+}
+
+#[allow(dead_code)]
+pub struct PamAuthTok {}
+
+impl PamItem for PamAuthTok {
+ fn item_type() -> PamItemType {
+ PAM_AUTHTOK
+ }
+}
+
+#[allow(dead_code)]
+pub struct PamOldAuthTok {}
+
+impl PamItem for PamOldAuthTok {
+ fn item_type() -> PamItemType {
+ PAM_OLDAUTHTOK
+ }
+}
--- /dev/null
+/*
+ MIT License
+
+ Copyright (c) 2015 TOZNY
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+/// Macro to generate the `extern "C"` entrypoint bindings needed by PAM
+///
+/// You can call `pam_hooks!(SomeType);` for any type that implements `PamHooks`
+///
+/// ## Examples:
+///
+/// Here is full example of a PAM module that would authenticate and authorize everybody:
+///
+/// ```
+/// #[macro_use]
+/// extern crate pam;
+///
+/// use pam::constants::{PamFlag, PamResultCode};
+/// use pam::module::{PamHandle, PamHooks};
+/// use std::ffi::CStr;
+///
+/// # fn main() {}
+/// struct MyPamModule;
+/// pam_hooks!(MyPamModule);
+///
+/// impl PamHooks for MyPamModule {
+/// fn sm_authenticate(pamh: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
+/// println!("Everybody is authenticated!");
+/// PamResultCode::PAM_SUCCESS
+/// }
+///
+/// fn acct_mgmt(pamh: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
+/// println!("Everybody is authorized!");
+/// PamResultCode::PAM_SUCCESS
+/// }
+/// }
+/// ```
+#[macro_export]
+macro_rules! pam_hooks {
+ ($ident:ident) => {
+ pub use self::pam_hooks_scope::*;
+ mod pam_hooks_scope {
+ use std::ffi::CStr;
+ use std::os::raw::{c_char, c_int};
+
+ use $crate::pam::constants::{PamFlag, PamResultCode};
+ use $crate::pam::module::{PamHandle, PamHooks};
+
+ fn extract_argv<'a>(argc: c_int, argv: *const *const c_char) -> Vec<&'a CStr> {
+ (0..argc)
+ .map(|o| unsafe { CStr::from_ptr(*argv.offset(o as isize) as *const c_char) })
+ .collect()
+ }
+
+ #[no_mangle]
+ pub extern "C" fn pam_sm_acct_mgmt(
+ pamh: &PamHandle,
+ flags: PamFlag,
+ argc: c_int,
+ argv: *const *const c_char,
+ ) -> PamResultCode {
+ let args = extract_argv(argc, argv);
+ super::$ident::acct_mgmt(pamh, args, flags)
+ }
+
+ #[no_mangle]
+ pub extern "C" fn pam_sm_authenticate(
+ pamh: &PamHandle,
+ flags: PamFlag,
+ argc: c_int,
+ argv: *const *const c_char,
+ ) -> PamResultCode {
+ let args = extract_argv(argc, argv);
+ super::$ident::sm_authenticate(pamh, args, flags)
+ }
+
+ #[no_mangle]
+ pub extern "C" fn pam_sm_chauthtok(
+ pamh: &PamHandle,
+ flags: PamFlag,
+ argc: c_int,
+ argv: *const *const c_char,
+ ) -> PamResultCode {
+ let args = extract_argv(argc, argv);
+ super::$ident::sm_chauthtok(pamh, args, flags)
+ }
+
+ #[no_mangle]
+ pub extern "C" fn pam_sm_close_session(
+ pamh: &PamHandle,
+ flags: PamFlag,
+ argc: c_int,
+ argv: *const *const c_char,
+ ) -> PamResultCode {
+ let args = extract_argv(argc, argv);
+ super::$ident::sm_close_session(pamh, args, flags)
+ }
+
+ #[no_mangle]
+ pub extern "C" fn pam_sm_open_session(
+ pamh: &PamHandle,
+ flags: PamFlag,
+ argc: c_int,
+ argv: *const *const c_char,
+ ) -> PamResultCode {
+ let args = extract_argv(argc, argv);
+ super::$ident::sm_open_session(pamh, args, flags)
+ }
+
+ #[no_mangle]
+ pub extern "C" fn pam_sm_setcred(
+ pamh: &PamHandle,
+ flags: PamFlag,
+ argc: c_int,
+ argv: *const *const c_char,
+ ) -> PamResultCode {
+ let args = extract_argv(argc, argv);
+ super::$ident::sm_setcred(pamh, args, flags)
+ }
+ }
+ };
+}
--- /dev/null
+/*
+ MIT License
+
+ Copyright (c) 2015 TOZNY
+ Copyright (c) 2020 William Brown <william@blackhats.net.au>
+ Copyright (c) 2024 David Mulder <dmulder@samba.org>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+//! Interface to the pluggable authentication module framework (PAM).
+//!
+//! The goal of this library is to provide a type-safe API that can be used to
+//! interact with PAM. The library is incomplete - currently it supports
+//! a subset of functions for use in a pam authentication module. A pam module
+//! is a shared library that is invoked to authenticate a user, or to perform
+//! other functions.
+//!
+//! For general information on writing pam modules, see
+//! [The Linux-PAM Module Writers' Guide][module-guide]
+//!
+//! [module-guide]: http://www.linux-pam.org/Linux-PAM-html/Linux-PAM_MWG.html
+//!
+//! A typical authentication module will define an external function called
+//! `pam_sm_authenticate()`, which will use functions in this library to
+//! interrogate the program that requested authentication for more information,
+//! and to render a result. For a working example that uses this library, see
+//! [toznyauth-pam][].
+//!
+//! [toznyauth-pam]: https://github.com/tozny/toznyauth-pam
+//!
+//! Note that constants that are normally read from pam header files are
+//! hard-coded in the `constants` module. The values there are taken from
+//! a Linux system. That means that it might take some work to get this library
+//! to work on other platforms.
+
+pub mod constants;
+pub mod conv;
+pub mod items;
+#[doc(hidden)]
+pub mod macros;
+pub mod module;
+
+use std::collections::BTreeSet;
+use std::convert::TryFrom;
+use std::ffi::CStr;
+
+use dbg::*;
+use param::LoadParm;
+use sock::{
+ stream_and_timeout, PamAuthRequest, PamAuthResponse, Request, Response,
+};
+
+use crate::pam::constants::*;
+use crate::pam::conv::PamConv;
+use crate::pam::module::{PamHandle, PamHooks};
+use crate::pam_hooks;
+use constants::PamResultCode;
+
+use std::thread;
+use std::time::Duration;
+
+fn install_subscriber(lp: &LoadParm, debug: bool) {
+ debuglevel_set!(if debug { DBGLVL_DEBUG } else { DBGLVL_ERR });
+ match lp.logfile() {
+ Ok(Some(logfile)) => {
+ debug_set_logfile(&logfile);
+ setup_logging(env!("CARGO_PKG_NAME"), DEBUG_FILE)
+ }
+ _ => setup_logging(env!("CARGO_PKG_NAME"), DEBUG_STDOUT),
+ }
+}
+
+#[derive(Debug)]
+struct Options {
+ debug: bool,
+ use_first_pass: bool,
+ ignore_unknown_user: bool,
+}
+
+impl TryFrom<&Vec<&CStr>> for Options {
+ type Error = ();
+
+ fn try_from(args: &Vec<&CStr>) -> Result<Self, Self::Error> {
+ let opts: Result<BTreeSet<&str>, _> =
+ args.iter().map(|cs| cs.to_str()).collect();
+ let gopts = match opts {
+ Ok(o) => o,
+ Err(e) => {
+ println!("Error in module args -> {:?}", e);
+ return Err(());
+ }
+ };
+
+ Ok(Options {
+ debug: gopts.contains("debug"),
+ use_first_pass: gopts.contains("use_first_pass"),
+ ignore_unknown_user: gopts.contains("ignore_unknown_user"),
+ })
+ }
+}
+
+pub struct PamHimmelblau;
+
+pam_hooks!(PamHimmelblau);
+
+macro_rules! match_sm_auth_client_response {
+ ($expr:expr, $opts:ident, $($pat:pat => $result:expr),*) => {
+ match $expr {
+ Ok(r) => match r {
+ $($pat => $result),*
+ Response::PamAuthStepResponse(PamAuthResponse::Success) => {
+ return PamResultCode::PAM_SUCCESS;
+ }
+ Response::PamAuthStepResponse(PamAuthResponse::Denied) => {
+ return PamResultCode::PAM_AUTH_ERR;
+ }
+ Response::PamAuthStepResponse(PamAuthResponse::Unknown) => {
+ if $opts.ignore_unknown_user {
+ return PamResultCode::PAM_IGNORE;
+ } else {
+ return PamResultCode::PAM_USER_UNKNOWN;
+ }
+ }
+ _ => {
+ // unexpected response.
+ DBG_ERR!("PAM_IGNORE, unexpected resolver response: {:?}", r);
+ return PamResultCode::PAM_IGNORE;
+ }
+ },
+ Err(e) => {
+ DBG_ERR!("PAM_IGNORE: {:?}", e);
+ return PamResultCode::PAM_IGNORE;
+ }
+ }
+ }
+}
+
+impl PamHooks for PamHimmelblau {
+ fn acct_mgmt(
+ pamh: &PamHandle,
+ args: Vec<&CStr>,
+ _flags: PamFlag,
+ ) -> PamResultCode {
+ let opts = match Options::try_from(&args) {
+ Ok(o) => o,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ let lp = match LoadParm::new(None) {
+ Ok(lp) => lp,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ install_subscriber(&lp, opts.debug);
+
+ let tty = pamh.get_tty();
+ let rhost = pamh.get_rhost();
+
+ DBG_DEBUG!("{:?} {:?} {:?} {:?} acct_mgmt", args, opts, tty, rhost);
+
+ let account_id = match pamh.get_user(None) {
+ Ok(aid) => aid,
+ Err(e) => {
+ DBG_ERR!("get_user: {:?}", e);
+ return e;
+ }
+ };
+
+ let req = Request::PamAccountAllowed(account_id);
+
+ let (mut stream, timeout) = match stream_and_timeout(&lp) {
+ Ok(res) => res,
+ Err(e) => {
+ DBG_ERR!("Error stream_and_timeout: {:?}", e);
+ return PamResultCode::PAM_SERVICE_ERR;
+ }
+ };
+
+ match stream.send(&req, timeout) {
+ Ok(r) => match r {
+ Response::PamStatus(Some(true)) => {
+ DBG_DEBUG!("PamResultCode::PAM_SUCCESS");
+ PamResultCode::PAM_SUCCESS
+ }
+ Response::PamStatus(Some(false)) => {
+ DBG_DEBUG!("PamResultCode::PAM_AUTH_ERR");
+ PamResultCode::PAM_AUTH_ERR
+ }
+ Response::PamStatus(None) => {
+ if opts.ignore_unknown_user {
+ DBG_DEBUG!("PamResultCode::PAM_IGNORE");
+ PamResultCode::PAM_IGNORE
+ } else {
+ DBG_DEBUG!("PamResultCode::PAM_USER_UNKNOWN");
+ PamResultCode::PAM_USER_UNKNOWN
+ }
+ }
+ _ => {
+ // unexpected response.
+ DBG_ERR!(
+ "PAM_IGNORE, unexpected resolver response: {:?}",
+ r
+ );
+ PamResultCode::PAM_IGNORE
+ }
+ },
+ Err(e) => {
+ DBG_ERR!("PamResultCode::PAM_IGNORE: {:?}", e);
+ PamResultCode::PAM_IGNORE
+ }
+ }
+ }
+
+ fn sm_authenticate(
+ pamh: &PamHandle,
+ args: Vec<&CStr>,
+ _flags: PamFlag,
+ ) -> PamResultCode {
+ let opts = match Options::try_from(&args) {
+ Ok(o) => o,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ let lp = match LoadParm::new(None) {
+ Ok(lp) => lp,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ install_subscriber(&lp, opts.debug);
+
+ let tty = pamh.get_tty();
+ let rhost = pamh.get_rhost();
+
+ DBG_DEBUG!(
+ "{:?} {:?} {:?} {:?} sm_authenticate",
+ args,
+ opts,
+ tty,
+ rhost
+ );
+
+ let account_id = match pamh.get_user(None) {
+ Ok(aid) => aid,
+ Err(e) => {
+ DBG_ERR!("get_user: {:?}", e);
+ return e;
+ }
+ };
+
+ let (mut stream, timeout) = match stream_and_timeout(&lp) {
+ Ok(res) => res,
+ Err(e) => {
+ DBG_ERR!("Error stream_and_timeout: {:?}", e);
+ return PamResultCode::PAM_SERVICE_ERR;
+ }
+ };
+
+ // Later we may need to move this to a function and call it as a oneshot for auth methods
+ // that don't require any authtoks at all. For example, imagine a user authed and they
+ // needed to follow a URL to continue. In that case, they would fail here because they
+ // didn't enter an authtok that they didn't need!
+ let mut authtok = match pamh.get_authtok() {
+ Ok(Some(v)) => Some(v),
+ Ok(None) => {
+ if opts.use_first_pass {
+ DBG_DEBUG!("Don't have an authtok, returning PAM_AUTH_ERR");
+ return PamResultCode::PAM_AUTH_ERR;
+ }
+ None
+ }
+ Err(e) => {
+ DBG_ERR!("get_authtok: {:?}", e);
+ return e;
+ }
+ };
+
+ let conv = match pamh.get_item::<PamConv>() {
+ Ok(conv) => conv,
+ Err(e) => {
+ DBG_ERR!("pam_conv: {:?}", e);
+ return e;
+ }
+ };
+
+ let mut req = Request::PamAuthenticateInit(account_id);
+
+ loop {
+ match_sm_auth_client_response!(stream.send(&req, timeout), opts,
+ Response::PamAuthStepResponse(PamAuthResponse::Password) => {
+ let mut consume_authtok = None;
+ // Swap the authtok out with a None, so it can only be consumed once.
+ // If it's already been swapped, we are just swapping two null pointers
+ // here effectively.
+ std::mem::swap(&mut authtok, &mut consume_authtok);
+ let cred = if let Some(cred) = consume_authtok {
+ cred
+ } else {
+ match conv.send(PAM_PROMPT_ECHO_OFF, "Password: ") {
+ Ok(password) => match password {
+ Some(cred) => cred,
+ None => {
+ DBG_DEBUG!("no password");
+ return PamResultCode::PAM_CRED_INSUFFICIENT;
+ }
+ },
+ Err(err) => {
+ DBG_DEBUG!("unable to get password");
+ return err;
+ }
+ }
+ };
+
+ // Now setup the request for the next loop.
+ req = Request::PamAuthenticateStep(PamAuthRequest::Password { cred });
+ continue;
+ },
+ Response::PamAuthStepResponse(PamAuthResponse::MFACode {
+ msg,
+ }) => {
+ match conv.send(PAM_TEXT_INFO, &msg) {
+ Ok(_) => {}
+ Err(err) => {
+ if opts.debug {
+ println!("Message prompt failed");
+ }
+ return err;
+ }
+ }
+ let cred = match conv.send(PAM_PROMPT_ECHO_OFF, "Code: ") {
+ Ok(password) => match password {
+ Some(cred) => cred,
+ None => {
+ DBG_DEBUG!("no mfa code");
+ return PamResultCode::PAM_CRED_INSUFFICIENT;
+ }
+ },
+ Err(err) => {
+ DBG_DEBUG!("unable to get mfa code");
+ return err;
+ }
+ };
+
+ // Now setup the request for the next loop.
+ req = Request::PamAuthenticateStep(PamAuthRequest::MFACode {
+ cred,
+ });
+ continue;
+ },
+ Response::PamAuthStepResponse(PamAuthResponse::MFAPoll {
+ msg,
+ polling_interval,
+ }) => {
+ match conv.send(PAM_TEXT_INFO, &msg) {
+ Ok(_) => {}
+ Err(err) => {
+ if opts.debug {
+ println!("Message prompt failed");
+ }
+ return err;
+ }
+ }
+
+ let mut poll_attempt = 0;
+ loop {
+ thread::sleep(Duration::from_secs(polling_interval.into()));
+ req = Request::PamAuthenticateStep(
+ PamAuthRequest::MFAPoll { poll_attempt }
+ );
+
+ match_sm_auth_client_response!(
+ stream.send(&req, timeout), opts,
+ Response::PamAuthStepResponse(
+ PamAuthResponse::MFAPollWait,
+ ) => {
+ // Continue polling if the daemon says to wait
+ poll_attempt += 1;
+ continue;
+ }
+ );
+ }
+ },
+ Response::PamAuthStepResponse(PamAuthResponse::SetupPin {
+ msg,
+ }) => {
+ match conv.send(PAM_TEXT_INFO, &msg) {
+ Ok(_) => {}
+ Err(err) => {
+ if opts.debug {
+ println!("Message prompt failed");
+ }
+ return err;
+ }
+ }
+
+ let mut pin;
+ let mut confirm;
+ loop {
+ pin = match conv.send(PAM_PROMPT_ECHO_OFF, "New PIN: ") {
+ Ok(password) => match password {
+ Some(cred) => cred,
+ None => {
+ DBG_DEBUG!("no pin");
+ return PamResultCode::PAM_CRED_INSUFFICIENT;
+ }
+ },
+ Err(err) => {
+ DBG_DEBUG!("unable to get pin");
+ return err;
+ }
+ };
+
+ confirm = match conv.send(PAM_PROMPT_ECHO_OFF, "Confirm PIN: ") {
+ Ok(password) => match password {
+ Some(cred) => cred,
+ None => {
+ DBG_DEBUG!("no confirmation pin");
+ return PamResultCode::PAM_CRED_INSUFFICIENT;
+ }
+ },
+ Err(err) => {
+ DBG_DEBUG!("unable to get confirmation pin");
+ return err;
+ }
+ };
+
+ if pin == confirm {
+ break;
+ } else {
+ match conv.send(PAM_TEXT_INFO, "Inputs did not match. Try again.") {
+ Ok(_) => {}
+ Err(err) => {
+ if opts.debug {
+ println!("Message prompt failed");
+ }
+ return err;
+ }
+ }
+ }
+ }
+
+ // Now setup the request for the next loop.
+ req = Request::PamAuthenticateStep(PamAuthRequest::SetupPin {
+ pin,
+ });
+ continue;
+ },
+ Response::PamAuthStepResponse(PamAuthResponse::Pin) => {
+ let mut consume_authtok = None;
+ // Swap the authtok out with a None, so it can only be consumed once.
+ // If it's already been swapped, we are just swapping two null pointers
+ // here effectively.
+ std::mem::swap(&mut authtok, &mut consume_authtok);
+ let cred = if let Some(cred) = consume_authtok {
+ cred
+ } else {
+ match conv.send(PAM_PROMPT_ECHO_OFF, "PIN: ") {
+ Ok(password) => match password {
+ Some(cred) => cred,
+ None => {
+ DBG_DEBUG!("no pin");
+ return PamResultCode::PAM_CRED_INSUFFICIENT;
+ }
+ },
+ Err(err) => {
+ DBG_DEBUG!("unable to get pin");
+ return err;
+ }
+ }
+ };
+
+ // Now setup the request for the next loop.
+ req = Request::PamAuthenticateStep(PamAuthRequest::Pin { pin: cred });
+ continue;
+ }
+ );
+ } // while true, continue calling PamAuthenticateStep until we get a decision.
+ }
+
+ fn sm_chauthtok(
+ _pamh: &PamHandle,
+ args: Vec<&CStr>,
+ _flags: PamFlag,
+ ) -> PamResultCode {
+ let opts = match Options::try_from(&args) {
+ Ok(o) => o,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ let lp = match LoadParm::new(None) {
+ Ok(lp) => lp,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ install_subscriber(&lp, opts.debug);
+
+ DBG_DEBUG!("{:?} {:?} sm_chauthtok", args, opts);
+
+ PamResultCode::PAM_IGNORE
+ }
+
+ fn sm_close_session(
+ _pamh: &PamHandle,
+ args: Vec<&CStr>,
+ _flags: PamFlag,
+ ) -> PamResultCode {
+ let opts = match Options::try_from(&args) {
+ Ok(o) => o,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ let lp = match LoadParm::new(None) {
+ Ok(lp) => lp,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ install_subscriber(&lp, opts.debug);
+
+ DBG_DEBUG!("{:?} {:?} sm_close_session", args, opts);
+
+ PamResultCode::PAM_SUCCESS
+ }
+
+ fn sm_open_session(
+ pamh: &PamHandle,
+ args: Vec<&CStr>,
+ _flags: PamFlag,
+ ) -> PamResultCode {
+ let opts = match Options::try_from(&args) {
+ Ok(o) => o,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ let lp = match LoadParm::new(None) {
+ Ok(lp) => lp,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ install_subscriber(&lp, opts.debug);
+
+ DBG_DEBUG!("{:?} {:?} sm_open_session", args, opts);
+
+ let account_id = match pamh.get_user(None) {
+ Ok(aid) => aid,
+ Err(e) => {
+ DBG_ERR!("get_user: {:?}", e);
+ return e;
+ }
+ };
+
+ let req = Request::PamAccountBeginSession(account_id);
+
+ let (mut stream, timeout) = match stream_and_timeout(&lp) {
+ Ok(res) => res,
+ Err(e) => {
+ DBG_ERR!("Error stream_and_timeout: {:?}", e);
+ return PamResultCode::PAM_SERVICE_ERR;
+ }
+ };
+
+ match stream.send(&req, timeout) {
+ Ok(Response::Success) => PamResultCode::PAM_SUCCESS,
+ other => {
+ DBG_DEBUG!("PAM_IGNORE: {:?}", other);
+ PamResultCode::PAM_IGNORE
+ }
+ }
+ }
+
+ fn sm_setcred(
+ _pamh: &PamHandle,
+ args: Vec<&CStr>,
+ _flags: PamFlag,
+ ) -> PamResultCode {
+ let opts = match Options::try_from(&args) {
+ Ok(o) => o,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ let lp = match LoadParm::new(None) {
+ Ok(lp) => lp,
+ Err(_) => return PamResultCode::PAM_SERVICE_ERR,
+ };
+
+ install_subscriber(&lp, opts.debug);
+
+ DBG_DEBUG!("{:?} {:?} sm_setcred", args, opts);
+
+ PamResultCode::PAM_SUCCESS
+ }
+}
--- /dev/null
+/*
+ MIT License
+
+ Copyright (c) 2015 TOZNY
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+//! Functions for use in pam modules.
+
+use std::ffi::{CStr, CString};
+use std::{mem, ptr};
+
+use libc::c_char;
+
+use crate::pam::constants::{
+ PamFlag, PamItemType, PamResultCode, PAM_AUTHTOK, PAM_RHOST, PAM_TTY,
+};
+
+/// Opaque type, used as a pointer when making pam API calls.
+///
+/// A module is invoked via an external function such as `pam_sm_authenticate`.
+/// Such a call provides a pam handle pointer. The same pointer should be given
+/// as an argument when making API calls.
+#[allow(missing_copy_implementations)]
+pub enum PamHandle {}
+
+#[allow(missing_copy_implementations)]
+enum PamItemT {}
+
+#[allow(missing_copy_implementations)]
+pub enum PamDataT {}
+
+#[link(name = "pam")]
+extern "C" {
+ fn pam_get_data(
+ pamh: *const PamHandle,
+ module_data_name: *const c_char,
+ data: &mut *const PamDataT,
+ ) -> PamResultCode;
+
+ fn pam_set_data(
+ pamh: *const PamHandle,
+ module_data_name: *const c_char,
+ data: *mut PamDataT,
+ cleanup: unsafe extern "C" fn(
+ pamh: *const PamHandle,
+ data: *mut PamDataT,
+ error_status: PamResultCode,
+ ),
+ ) -> PamResultCode;
+
+ fn pam_get_item(
+ pamh: *const PamHandle,
+ item_type: PamItemType,
+ item: &mut *const PamItemT,
+ ) -> PamResultCode;
+
+ fn pam_set_item(
+ pamh: *mut PamHandle,
+ item_type: PamItemType,
+ item: &PamItemT,
+ ) -> PamResultCode;
+
+ fn pam_get_user(
+ pamh: *const PamHandle,
+ user: &*mut c_char,
+ prompt: *const c_char,
+ ) -> PamResultCode;
+}
+
+/// # Safety
+///
+/// We're doing what we can for this one, but it's FFI.
+pub unsafe extern "C" fn cleanup<T>(
+ _: *const PamHandle,
+ c_data: *mut PamDataT,
+ _: PamResultCode,
+) {
+ let c_data = Box::from_raw(c_data);
+ let data: Box<T> = mem::transmute(c_data);
+ mem::drop(data);
+}
+
+pub type PamResult<T> = Result<T, PamResultCode>;
+
+/// # Safety
+///
+/// Type-level mapping for safely retrieving values with `get_item`.
+///
+/// See `pam_get_item` in
+/// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
+pub trait PamItem {
+ /// Maps a Rust type to a pam constant.
+ ///
+ /// For example, the type PamConv maps to the constant PAM_CONV. The pam
+ /// API contract specifies that when the API function `pam_get_item` is
+ /// called with the constant PAM_CONV, it will return a value of type
+ /// `PamConv`.
+ fn item_type() -> PamItemType;
+}
+
+impl PamHandle {
+ /// # Safety
+ ///
+ /// Gets some value, identified by `key`, that has been set by the module
+ /// previously.
+ ///
+ /// See `pam_get_data` in
+ /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
+ pub unsafe fn get_data<'a, T>(&'a self, key: &str) -> PamResult<&'a T> {
+ let c_key = CString::new(key).unwrap();
+ let mut ptr: *const PamDataT = ptr::null();
+ let res = pam_get_data(self, c_key.as_ptr(), &mut ptr);
+ if PamResultCode::PAM_SUCCESS == res && !ptr.is_null() {
+ let typed_ptr: *const T = ptr as *const T;
+ let data: &T = &*typed_ptr;
+ Ok(data)
+ } else {
+ Err(res)
+ }
+ }
+
+ /// Stores a value that can be retrieved later with `get_data`. The value lives
+ /// as long as the current pam cycle.
+ ///
+ /// See `pam_set_data` in
+ /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
+ pub fn set_data<T>(&self, key: &str, data: Box<T>) -> PamResult<()> {
+ let c_key = CString::new(key).unwrap();
+ let res = unsafe {
+ let c_data: Box<PamDataT> = mem::transmute(data);
+ let c_data = Box::into_raw(c_data);
+ pam_set_data(self, c_key.as_ptr(), c_data, cleanup::<T>)
+ };
+ if PamResultCode::PAM_SUCCESS == res {
+ Ok(())
+ } else {
+ Err(res)
+ }
+ }
+
+ /// Retrieves a value that has been set, possibly by the pam client. This is
+ /// particularly useful for getting a `PamConv` reference.
+ ///
+ /// See `pam_get_item` in
+ /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
+ pub fn get_item<'a, T: PamItem>(&self) -> PamResult<&'a T> {
+ let mut ptr: *const PamItemT = ptr::null();
+ let (res, item) = unsafe {
+ let r = pam_get_item(self, T::item_type(), &mut ptr);
+ let typed_ptr: *const T = ptr as *const T;
+ let t: &T = &*typed_ptr;
+ (r, t)
+ };
+ if PamResultCode::PAM_SUCCESS == res {
+ Ok(item)
+ } else {
+ Err(res)
+ }
+ }
+
+ /// Sets a value in the pam context. The value can be retrieved using
+ /// `get_item`.
+ ///
+ /// Note that all items are strings, except `PAM_CONV` and `PAM_FAIL_DELAY`.
+ ///
+ /// See `pam_set_item` in
+ /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
+ pub fn set_item_str<T: PamItem>(&mut self, item: &str) -> PamResult<()> {
+ let c_item = CString::new(item).unwrap();
+
+ let res = unsafe {
+ pam_set_item(
+ self,
+ T::item_type(),
+ // unwrapping is okay here, as c_item will not be a NULL
+ // pointer
+ (c_item.as_ptr() as *const PamItemT).as_ref().unwrap(),
+ )
+ };
+ if PamResultCode::PAM_SUCCESS == res {
+ Ok(())
+ } else {
+ Err(res)
+ }
+ }
+
+ /// Retrieves the name of the user who is authenticating or logging in.
+ ///
+ /// This is really a specialization of `get_item`.
+ ///
+ /// See `pam_get_user` in
+ /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
+ pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> {
+ let ptr: *mut c_char = ptr::null_mut();
+ let res = match prompt {
+ Some(p) => {
+ let c_prompt = CString::new(p).unwrap();
+ unsafe { pam_get_user(self, &ptr, c_prompt.as_ptr()) }
+ }
+ None => unsafe { pam_get_user(self, &ptr, ptr::null()) },
+ };
+
+ if PamResultCode::PAM_SUCCESS == res && !ptr.is_null() {
+ let const_ptr = ptr as *const c_char;
+ let bytes = unsafe { CStr::from_ptr(const_ptr).to_bytes() };
+ String::from_utf8(bytes.to_vec())
+ .map_err(|_| PamResultCode::PAM_CONV_ERR)
+ } else {
+ Err(res)
+ }
+ }
+
+ pub fn get_authtok(&self) -> PamResult<Option<String>> {
+ let mut ptr: *const PamItemT = ptr::null();
+ let (res, item) = unsafe {
+ let r = pam_get_item(self, PAM_AUTHTOK, &mut ptr);
+ let t = if PamResultCode::PAM_SUCCESS == r && !ptr.is_null() {
+ let typed_ptr: *const c_char = ptr as *const c_char;
+ Some(CStr::from_ptr(typed_ptr).to_string_lossy().into_owned())
+ } else {
+ None
+ };
+ (r, t)
+ };
+ if PamResultCode::PAM_SUCCESS == res {
+ Ok(item)
+ } else {
+ Err(res)
+ }
+ }
+
+ pub fn get_tty(&self) -> PamResult<Option<String>> {
+ let mut ptr: *const PamItemT = ptr::null();
+ let (res, item) = unsafe {
+ let r = pam_get_item(self, PAM_TTY, &mut ptr);
+ let t = if PamResultCode::PAM_SUCCESS == r && !ptr.is_null() {
+ let typed_ptr: *const c_char = ptr as *const c_char;
+ Some(CStr::from_ptr(typed_ptr).to_string_lossy().into_owned())
+ } else {
+ None
+ };
+ (r, t)
+ };
+ if PamResultCode::PAM_SUCCESS == res {
+ Ok(item)
+ } else {
+ Err(res)
+ }
+ }
+
+ pub fn get_rhost(&self) -> PamResult<Option<String>> {
+ let mut ptr: *const PamItemT = ptr::null();
+ let (res, item) = unsafe {
+ let r = pam_get_item(self, PAM_RHOST, &mut ptr);
+ let t = if PamResultCode::PAM_SUCCESS == r && !ptr.is_null() {
+ let typed_ptr: *const c_char = ptr as *const c_char;
+ Some(CStr::from_ptr(typed_ptr).to_string_lossy().into_owned())
+ } else {
+ None
+ };
+ (r, t)
+ };
+ if PamResultCode::PAM_SUCCESS == res {
+ Ok(item)
+ } else {
+ Err(res)
+ }
+ }
+}
+
+/// Provides functions that are invoked by the entrypoints generated by the
+/// [`pam_hooks!` macro](../macro.pam_hooks.html).
+///
+/// All of hooks are ignored by PAM dispatch by default given the default return value of `PAM_IGNORE`.
+/// Override any functions that you want to handle with your module. See `man pam(3)`.
+#[allow(unused_variables)]
+pub trait PamHooks {
+ /// This function performs the task of establishing whether the user is permitted to gain access at
+ /// this time. It should be understood that the user has previously been validated by an
+ /// authentication module. This function checks for other things. Such things might be: the time of
+ /// day or the date, the terminal line, remote hostname, etc. This function may also determine
+ /// things like the expiration on passwords, and respond that the user change it before continuing.
+ fn acct_mgmt(
+ pamh: &PamHandle,
+ args: Vec<&CStr>,
+ flags: PamFlag,
+ ) -> PamResultCode {
+ PamResultCode::PAM_IGNORE
+ }
+
+ /// This function performs the task of authenticating the user.
+ fn sm_authenticate(
+ pamh: &PamHandle,
+ args: Vec<&CStr>,
+ flags: PamFlag,
+ ) -> PamResultCode {
+ PamResultCode::PAM_IGNORE
+ }
+
+ /// This function is used to (re-)set the authentication token of the user.
+ ///
+ /// The PAM library calls this function twice in succession. The first time with
+ /// PAM_PRELIM_CHECK and then, if the module does not return PAM_TRY_AGAIN, subsequently with
+ /// PAM_UPDATE_AUTHTOK. It is only on the second call that the authorization token is
+ /// (possibly) changed.
+ fn sm_chauthtok(
+ pamh: &PamHandle,
+ args: Vec<&CStr>,
+ flags: PamFlag,
+ ) -> PamResultCode {
+ PamResultCode::PAM_IGNORE
+ }
+
+ /// This function is called to terminate a session.
+ fn sm_close_session(
+ pamh: &PamHandle,
+ args: Vec<&CStr>,
+ flags: PamFlag,
+ ) -> PamResultCode {
+ PamResultCode::PAM_IGNORE
+ }
+
+ /// This function is called to commence a session.
+ fn sm_open_session(
+ pamh: &PamHandle,
+ args: Vec<&CStr>,
+ flags: PamFlag,
+ ) -> PamResultCode {
+ PamResultCode::PAM_IGNORE
+ }
+
+ /// This function performs the task of altering the credentials of the user with respect to the
+ /// corresponding authorization scheme. Generally, an authentication module may have access to more
+ /// information about a user than their authentication token. This function is used to make such
+ /// information available to the application. It should only be called after the user has been
+ /// authenticated but before a session has been established.
+ fn sm_setcred(
+ pamh: &PamHandle,
+ args: Vec<&CStr>,
+ flags: PamFlag,
+ ) -> PamResultCode {
+ PamResultCode::PAM_IGNORE
+ }
+}
--- /dev/null
+#!/usr/bin/env python
+
+bld.SAMBA_RUST_LIBRARY('libpam_himmelblau.so',
+ source='src/lib.rs src/pam/constants.rs src/pam/conv.rs src/pam/items.rs src/pam/items.rs src/pam/macros.rs src/pam/mod.rs src/pam/module.rs ../sock/src/lib.rs ../ntstatus_gen/src/lib.rs ../param/src/lib.rs ../version/src/lib.rs')
version.workspace = true
[dependencies]
-libc = "0.2.155"
+libc.workspace = true
libnss = "0.8.0"
ntstatus_gen.workspace = true
param.workspace = true
Password,
MFACode { msg: String },
MFAPoll { msg: String, polling_interval: u32 },
+ MFAPollWait,
SetupPin { msg: String },
Pin,
}
[dependencies]
chelps = { workspace = true }
dbg.workspace = true
-libc = "0.2.155"
+libc.workspace = true
ntstatus_gen.workspace = true
[build-dependencies]
[dependencies]
chelps.workspace = true
-libc = "0.2.153"
+libc.workspace = true
[build-dependencies]
cc = "1.0.97"
source='src/main.rs param/src/lib.rs chelps/src/lib.rs dbg/src/lib.rs ntstatus_gen/src/lib.rs sock/src/lib.rs tdb/src/lib.rs version/src/lib.rs')
bld.RECURSE('nss')
+bld.RECURSE('pam')