From: David Mulder Date: Thu, 1 Aug 2024 19:36:58 +0000 (-0600) Subject: Add PAM module for himmelblaud X-Git-Tag: tdb-1.4.13~918 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f6a22875d15e5c5d740deeb323eb9fe539fbf6c1;p=thirdparty%2Fsamba.git Add PAM module for himmelblaud Signed-off-by: David Mulder Reviewed-by: Alexander Bokovoy --- diff --git a/himmelblaud/Cargo.toml b/himmelblaud/Cargo.toml index 6a5409487ef..56daa81fbe7 100644 --- a/himmelblaud/Cargo.toml +++ b/himmelblaud/Cargo.toml @@ -28,7 +28,7 @@ bytes = "1.6.1" futures = "0.3.30" serde = "1.0.204" idmap = { workspace = true } -libc = "0.2.155" +libc = { workspace = true } [build-dependencies] version = { path = "version" } @@ -36,7 +36,7 @@ version = { path = "version" } [workspace] members = [ "chelps", "dbg", "idmap", - "nss", "ntstatus_gen", + "nss", "ntstatus_gen", "pam", "param", "sock", "tdb", "version", ] @@ -48,3 +48,4 @@ sock = { path = "sock" } ntstatus_gen = { path = "ntstatus_gen" } tdb = { path = "tdb" } idmap = { path = "idmap" } +libc = "0.2.155" diff --git a/himmelblaud/idmap/Cargo.toml b/himmelblaud/idmap/Cargo.toml index 6d81b43b308..8df127f0507 100644 --- a/himmelblaud/idmap/Cargo.toml +++ b/himmelblaud/idmap/Cargo.toml @@ -8,7 +8,7 @@ version.workspace = true [dependencies] chelps.workspace = true dbg.workspace = true -libc = "0.2.153" +libc.workspace = true [build-dependencies] cc = "1.0.97" diff --git a/himmelblaud/nss/Cargo.toml b/himmelblaud/nss/Cargo.toml index c182b27d51a..697015459cd 100644 --- a/himmelblaud/nss/Cargo.toml +++ b/himmelblaud/nss/Cargo.toml @@ -11,7 +11,7 @@ crate-type = [ "cdylib" ] path = "src/lib.rs" [dependencies] -libc = "0.2.155" +libc.workspace = true libnss = "0.8.0" ntstatus_gen.workspace = true param = { workspace = true } diff --git a/himmelblaud/pam/Cargo.toml b/himmelblaud/pam/Cargo.toml new file mode 100644 index 00000000000..509468fd6e1 --- /dev/null +++ b/himmelblaud/pam/Cargo.toml @@ -0,0 +1,23 @@ +[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" diff --git a/himmelblaud/pam/build.rs b/himmelblaud/pam/build.rs new file mode 100644 index 00000000000..3c23ee3f114 --- /dev/null +++ b/himmelblaud/pam/build.rs @@ -0,0 +1,9 @@ +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"); +} diff --git a/himmelblaud/pam/src/lib.rs b/himmelblaud/pam/src/lib.rs new file mode 100644 index 00000000000..7a40284252b --- /dev/null +++ b/himmelblaud/pam/src/lib.rs @@ -0,0 +1,43 @@ +/* + 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::*; diff --git a/himmelblaud/pam/src/pam/constants.rs b/himmelblaud/pam/src/pam/constants.rs new file mode 100644 index 00000000000..a626651be73 --- /dev/null +++ b/himmelblaud/pam/src/pam/constants.rs @@ -0,0 +1,118 @@ +/* + 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, +} diff --git a/himmelblaud/pam/src/pam/conv.rs b/himmelblaud/pam/src/pam/conv.rs new file mode 100644 index 00000000000..8fa8b46cedb --- /dev/null +++ b/himmelblaud/pam/src/pam/conv.rs @@ -0,0 +1,112 @@ +/* + 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> { + 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 + } +} diff --git a/himmelblaud/pam/src/pam/items.rs b/himmelblaud/pam/src/pam/items.rs new file mode 100644 index 00000000000..14bcade14b8 --- /dev/null +++ b/himmelblaud/pam/src/pam/items.rs @@ -0,0 +1,101 @@ +/* + 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 + } +} diff --git a/himmelblaud/pam/src/pam/macros.rs b/himmelblaud/pam/src/pam/macros.rs new file mode 100644 index 00000000000..6e3f6d90214 --- /dev/null +++ b/himmelblaud/pam/src/pam/macros.rs @@ -0,0 +1,140 @@ +/* + 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) + } + } + }; +} diff --git a/himmelblaud/pam/src/pam/mod.rs b/himmelblaud/pam/src/pam/mod.rs new file mode 100755 index 00000000000..404ecf124f0 --- /dev/null +++ b/himmelblaud/pam/src/pam/mod.rs @@ -0,0 +1,606 @@ +/* + MIT License + + Copyright (c) 2015 TOZNY + Copyright (c) 2020 William Brown + Copyright (c) 2024 David Mulder + + 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 { + let opts: Result, _> = + 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::() { + 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 + } +} diff --git a/himmelblaud/pam/src/pam/module.rs b/himmelblaud/pam/src/pam/module.rs new file mode 100755 index 00000000000..df2eade70fd --- /dev/null +++ b/himmelblaud/pam/src/pam/module.rs @@ -0,0 +1,361 @@ +/* + 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( + _: *const PamHandle, + c_data: *mut PamDataT, + _: PamResultCode, +) { + let c_data = Box::from_raw(c_data); + let data: Box = mem::transmute(c_data); + mem::drop(data); +} + +pub type PamResult = Result; + +/// # Safety +/// +/// Type-level mapping for safely retrieving values with `get_item`. +/// +/// See `pam_get_item` in +/// +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 + /// + 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 + /// + pub fn set_data(&self, key: &str, data: Box) -> PamResult<()> { + let c_key = CString::new(key).unwrap(); + let res = unsafe { + let c_data: Box = mem::transmute(data); + let c_data = Box::into_raw(c_data); + pam_set_data(self, c_key.as_ptr(), c_data, cleanup::) + }; + 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 + /// + 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 + /// + pub fn set_item_str(&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 + /// + pub fn get_user(&self, prompt: Option<&str>) -> PamResult { + 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> { + 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> { + 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> { + 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 + } +} diff --git a/himmelblaud/pam/wscript_build b/himmelblaud/pam/wscript_build new file mode 100644 index 00000000000..2ab73d94bc1 --- /dev/null +++ b/himmelblaud/pam/wscript_build @@ -0,0 +1,4 @@ +#!/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') diff --git a/himmelblaud/sock/Cargo.toml b/himmelblaud/sock/Cargo.toml index f2a788ec633..7f00c71a0f9 100644 --- a/himmelblaud/sock/Cargo.toml +++ b/himmelblaud/sock/Cargo.toml @@ -6,7 +6,7 @@ homepage.workspace = true version.workspace = true [dependencies] -libc = "0.2.155" +libc.workspace = true libnss = "0.8.0" ntstatus_gen.workspace = true param.workspace = true diff --git a/himmelblaud/sock/src/proto.rs b/himmelblaud/sock/src/proto.rs index ad40c50486f..728637ac807 100644 --- a/himmelblaud/sock/src/proto.rs +++ b/himmelblaud/sock/src/proto.rs @@ -98,6 +98,7 @@ pub enum PamAuthResponse { Password, MFACode { msg: String }, MFAPoll { msg: String, polling_interval: u32 }, + MFAPollWait, SetupPin { msg: String }, Pin, } diff --git a/himmelblaud/tdb/Cargo.toml b/himmelblaud/tdb/Cargo.toml index da3d076abd2..ef96e76c5e1 100644 --- a/himmelblaud/tdb/Cargo.toml +++ b/himmelblaud/tdb/Cargo.toml @@ -8,7 +8,7 @@ version.workspace = true [dependencies] chelps = { workspace = true } dbg.workspace = true -libc = "0.2.155" +libc.workspace = true ntstatus_gen.workspace = true [build-dependencies] diff --git a/himmelblaud/version/Cargo.toml b/himmelblaud/version/Cargo.toml index c7e6aa8ff7c..046dbd074ed 100644 --- a/himmelblaud/version/Cargo.toml +++ b/himmelblaud/version/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" [dependencies] chelps.workspace = true -libc = "0.2.153" +libc.workspace = true [build-dependencies] cc = "1.0.97" diff --git a/himmelblaud/wscript_build b/himmelblaud/wscript_build index d269c08b83c..1916cf64bcb 100644 --- a/himmelblaud/wscript_build +++ b/himmelblaud/wscript_build @@ -4,3 +4,4 @@ bld.SAMBA_RUST_BINARY('himmelblaud', 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')