summaryrefslogtreecommitdiff
path: root/src-repo/src/raw_conv.rs
diff options
context:
space:
mode:
authorDennis Kobert <dennis@kobert.dev>2026-03-11 14:15:45 +0100
committerDennis Kobert <dennis@kobert.dev>2026-03-11 14:16:07 +0100
commita9a74679fbec07cf158f2295513e3d4c4adc2666 (patch)
tree5dcac28283ef53c2869bdedb9badff0896900bb6 /src-repo/src/raw_conv.rs
parent116bd5a46adc7e9caa27f58b19cafe68d3e085f0 (diff)
Port pam-any from pam-bindings to nonstick crateHEADmain
The pam-bindings crate has a critical bug where pam_try! returns PAM_SUCCESS on error in release builds (anowell/pam-rs#16), causing authentication to always succeed. This replaces it with nonstick and a custom pam_client module with raw PAM FFI for thread-safe conversation forwarding.
Diffstat (limited to 'src-repo/src/raw_conv.rs')
-rw-r--r--src-repo/src/raw_conv.rs116
1 files changed, 116 insertions, 0 deletions
diff --git a/src-repo/src/raw_conv.rs b/src-repo/src/raw_conv.rs
new file mode 100644
index 0000000..fe93b64
--- /dev/null
+++ b/src-repo/src/raw_conv.rs
@@ -0,0 +1,116 @@
+use std::ffi::{CStr, CString};
+use std::ptr;
+
+const PAM_CONV: libc::c_int = 5;
+const PAM_PROMPT_ECHO_OFF: libc::c_int = 1;
+const PAM_PROMPT_ECHO_ON: libc::c_int = 2;
+const PAM_ERROR_MSG: libc::c_int = 3;
+const PAM_TEXT_INFO: libc::c_int = 4;
+
+#[repr(C)]
+struct PamMessage {
+ msg_style: libc::c_int,
+ msg: *const libc::c_char,
+}
+
+#[repr(C)]
+struct PamResponse {
+ resp: *mut libc::c_char,
+ resp_retcode: libc::c_int,
+}
+
+type PamConvFn = unsafe extern "C" fn(
+ num_msg: libc::c_int,
+ msg: *mut *const PamMessage,
+ resp: *mut *mut PamResponse,
+ appdata_ptr: *mut libc::c_void,
+) -> libc::c_int;
+
+#[repr(C)]
+struct PamConv {
+ conv: PamConvFn,
+ appdata_ptr: *mut libc::c_void,
+}
+
+extern "C" {
+ fn pam_get_item(
+ pamh: *const libc::c_void,
+ item_type: libc::c_int,
+ item: *mut *const libc::c_void,
+ ) -> libc::c_int;
+}
+
+/// Thread-safe wrapper around a raw PAM conversation function pointer.
+pub struct RawConv {
+ conv_fn: PamConvFn,
+ appdata_ptr: *mut libc::c_void,
+}
+
+unsafe impl Send for RawConv {}
+
+impl RawConv {
+ /// Extract the raw PAM conversation from a PAM handle.
+ pub fn from_pam_handle(pamh: *mut libc::c_void) -> Option<Self> {
+ unsafe {
+ let mut conv_ptr: *const libc::c_void = ptr::null();
+ let ret = pam_get_item(pamh, PAM_CONV, &mut conv_ptr);
+ if ret != 0 || conv_ptr.is_null() {
+ return None;
+ }
+ let pam_conv = &*(conv_ptr as *const PamConv);
+ Some(RawConv {
+ conv_fn: pam_conv.conv,
+ appdata_ptr: pam_conv.appdata_ptr,
+ })
+ }
+ }
+
+ /// Send a message through the PAM conversation.
+ /// Returns the response string for prompt types, or None for info/error.
+ pub fn send(&self, msg_style: libc::c_int, msg: &str) -> Result<Option<CString>, ()> {
+ let c_msg = CString::new(msg).map_err(|_| ())?;
+ let pam_msg = PamMessage {
+ msg_style,
+ msg: c_msg.as_ptr(),
+ };
+ let mut msg_ptr: *const PamMessage = &pam_msg;
+ let mut resp_ptr: *mut PamResponse = ptr::null_mut();
+
+ let ret = unsafe { (self.conv_fn)(1, &mut msg_ptr, &mut resp_ptr, self.appdata_ptr) };
+
+ if ret != 0 {
+ return Err(());
+ }
+
+ if resp_ptr.is_null() {
+ return Ok(None);
+ }
+
+ let response = unsafe {
+ let resp = &*resp_ptr;
+ let result = if resp.resp.is_null() {
+ None
+ } else {
+ let s = CStr::from_ptr(resp.resp).to_owned();
+ libc::free(resp.resp as *mut libc::c_void);
+ Some(s)
+ };
+ libc::free(resp_ptr as *mut libc::c_void);
+ result
+ };
+
+ Ok(response)
+ }
+
+ pub fn send_prompt(&self, msg: &str) -> Result<Option<CString>, ()> {
+ self.send(PAM_PROMPT_ECHO_OFF, msg)
+ }
+
+ pub fn send_info(&self, msg: &str) -> Result<Option<CString>, ()> {
+ self.send(PAM_TEXT_INFO, msg)
+ }
+
+ pub fn send_error(&self, msg: &str) -> Result<Option<CString>, ()> {
+ self.send(PAM_ERROR_MSG, msg)
+ }
+}