]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
Add PAM module for himmelblaud
authorDavid Mulder <dmulder@samba.org>
Thu, 1 Aug 2024 19:36:58 +0000 (13:36 -0600)
committerDavid Mulder <dmulder@samba.org>
Wed, 23 Oct 2024 14:21:33 +0000 (14:21 +0000)
Signed-off-by: David Mulder <dmulder@samba.org>
Reviewed-by: Alexander Bokovoy <ab@samba.org>
18 files changed:
himmelblaud/Cargo.toml
himmelblaud/idmap/Cargo.toml
himmelblaud/nss/Cargo.toml
himmelblaud/pam/Cargo.toml [new file with mode: 0644]
himmelblaud/pam/build.rs [new file with mode: 0644]
himmelblaud/pam/src/lib.rs [new file with mode: 0644]
himmelblaud/pam/src/pam/constants.rs [new file with mode: 0644]
himmelblaud/pam/src/pam/conv.rs [new file with mode: 0644]
himmelblaud/pam/src/pam/items.rs [new file with mode: 0644]
himmelblaud/pam/src/pam/macros.rs [new file with mode: 0644]
himmelblaud/pam/src/pam/mod.rs [new file with mode: 0755]
himmelblaud/pam/src/pam/module.rs [new file with mode: 0755]
himmelblaud/pam/wscript_build [new file with mode: 0644]
himmelblaud/sock/Cargo.toml
himmelblaud/sock/src/proto.rs
himmelblaud/tdb/Cargo.toml
himmelblaud/version/Cargo.toml
himmelblaud/wscript_build

index 6a5409487ef1547cecf2f675470d2beec109a873..56daa81fbe7ace62c318f008fd4ff477354cf6b8 100644 (file)
@@ -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"
index 6d81b43b3088108a884d4a6b75a752f33736ea2e..8df127f0507dfc8dc0d0206678e4d3a86a03bd7b 100644 (file)
@@ -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"
index c182b27d51a773d3c97dac6806262b370bedd779..697015459cdb6724da9de6a487cc09e3587b2022 100644 (file)
@@ -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 (file)
index 0000000..509468f
--- /dev/null
@@ -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 (file)
index 0000000..3c23ee3
--- /dev/null
@@ -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 (file)
index 0000000..7a40284
--- /dev/null
@@ -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 (file)
index 0000000..a626651
--- /dev/null
@@ -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 (file)
index 0000000..8fa8b46
--- /dev/null
@@ -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<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
+    }
+}
diff --git a/himmelblaud/pam/src/pam/items.rs b/himmelblaud/pam/src/pam/items.rs
new file mode 100644 (file)
index 0000000..14bcade
--- /dev/null
@@ -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 (file)
index 0000000..6e3f6d9
--- /dev/null
@@ -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 (executable)
index 0000000..404ecf1
--- /dev/null
@@ -0,0 +1,606 @@
+/*
+   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
+    }
+}
diff --git a/himmelblaud/pam/src/pam/module.rs b/himmelblaud/pam/src/pam/module.rs
new file mode 100755 (executable)
index 0000000..df2eade
--- /dev/null
@@ -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<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
+    }
+}
diff --git a/himmelblaud/pam/wscript_build b/himmelblaud/pam/wscript_build
new file mode 100644 (file)
index 0000000..2ab73d9
--- /dev/null
@@ -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')
index f2a788ec633b58b8e5df7bc1ba63ed1cd411ffe6..7f00c71a0f93d9cce3499ce011b8c67d7b094bf1 100644 (file)
@@ -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
index ad40c50486fb00fb75fadd8772af5be678503953..728637ac807864febbe204ad6afdb9923affed65 100644 (file)
@@ -98,6 +98,7 @@ pub enum PamAuthResponse {
     Password,
     MFACode { msg: String },
     MFAPoll { msg: String, polling_interval: u32 },
+    MFAPollWait,
     SetupPin { msg: String },
     Pin,
 }
index da3d076abd2d696e9c263e44f70410ad678a2f41..ef96e76c5e12a54d28279500d883d4968ccefc36 100644 (file)
@@ -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]
index c7e6aa8ff7ccadaf54d7e311e55ad2bb8bcda3ae..046dbd074ed6bcaa77a750b153824e162c0d4fd9 100644 (file)
@@ -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"
index d269c08b83c6ec635d3e923754a7f2dc1605477d..1916cf64bcb5b64a2fbb379cff0f3ceef773b5e3 100644 (file)
@@ -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')