git.lucas.co / cce-display-manager
login greeter
git clone https://git.lucas.co/cce-display-manager.git

commita224d39196d34b94b2f16df42d0998b51a7ba656
parent8fcc7a2f18
authorLucas Galante <[email protected]>
date2026-05-28 21:46
Pass correct username to pam_start via custom PamSession, and propagate PAM env variables to spawned user session

 Cargo.toml  |   1 +
 src/main.rs | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 153 insertions(+), 7 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index be59b00..18b3549 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2021"
 [dependencies]
 clear-ui = { path = "../clear-ui" }
 pam = "0.7.0"
+pam-sys = "0.5.6"
 libc = "0.2"
 users = "0.8.1"
 toml = "0.8"
diff --git a/src/main.rs b/src/main.rs
index e14cde3..29aaeb0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1429,6 +1429,144 @@ fn run_greeter() {
     std::process::exit(1);
 }
 
+struct PamSessionData {
+    username: String,
+    password: String,
+}
+
+extern "C" fn pam_conversation_fn(
+    num_msg: libc::c_int,
+    msg: *mut *mut pam_sys::PamMessage,
+    out_resp: *mut *mut pam_sys::PamResponse,
+    appdata_ptr: *mut libc::c_void,
+) -> libc::c_int {
+    let data = unsafe { &*(appdata_ptr as *const PamSessionData) };
+    let resp_size = std::mem::size_of::<pam_sys::PamResponse>();
+    let resp = unsafe { libc::calloc(num_msg as usize, resp_size) as *mut pam_sys::PamResponse };
+    if resp.is_null() {
+        return pam_sys::PamReturnCode::BUF_ERR as libc::c_int;
+    }
+
+    for i in 0..num_msg as isize {
+        unsafe {
+            let m = &**msg.offset(i);
+            let r = &mut *resp.offset(i);
+            if m.msg_style == pam_sys::PamMessageStyle::PROMPT_ECHO_ON as libc::c_int {
+                let user_c = std::ffi::CString::new(data.username.clone()).unwrap();
+                r.resp = libc::strdup(user_c.as_ptr());
+            } else if m.msg_style == pam_sys::PamMessageStyle::PROMPT_ECHO_OFF as libc::c_int {
+                let pass_c = std::ffi::CString::new(data.password.clone()).unwrap();
+                r.resp = libc::strdup(pass_c.as_ptr());
+            }
+        }
+    }
+
+    unsafe { *out_resp = resp };
+    pam_sys::PamReturnCode::SUCCESS as libc::c_int
+}
+
+struct PamSession {
+    handle: *mut pam_sys::PamHandle,
+    _data: Box<PamSessionData>,
+    has_open_session: bool,
+}
+
+impl PamSession {
+    fn new(service: &str, username: &str, password: &str) -> Result<Self, pam_sys::PamReturnCode> {
+        let mut handle: *mut pam_sys::PamHandle = std::ptr::null_mut();
+        let data = Box::new(PamSessionData {
+            username: username.to_string(),
+            password: password.to_string(),
+        });
+        
+        let conv = pam_sys::PamConversation {
+            conv: Some(pam_conversation_fn),
+            data_ptr: &*data as *const PamSessionData as *mut libc::c_void,
+        };
+
+        let rc = pam_sys::start(service, Some(username), &conv, &mut handle);
+        if rc != pam_sys::PamReturnCode::SUCCESS {
+            return Err(rc);
+        }
+
+        Ok(Self { handle, _data: data, has_open_session: false })
+    }
+
+    fn authenticate(&mut self) -> Result<(), pam_sys::PamReturnCode> {
+        unsafe {
+            let rc = pam_sys::authenticate(&mut *self.handle, pam_sys::PamFlag::NONE);
+            if rc != pam_sys::PamReturnCode::SUCCESS {
+                return Err(rc);
+            }
+
+            let rc = pam_sys::acct_mgmt(&mut *self.handle, pam_sys::PamFlag::NONE);
+            if rc != pam_sys::PamReturnCode::SUCCESS {
+                return Err(rc);
+            }
+        }
+        Ok(())
+    }
+
+    fn open_session(&mut self) -> Result<(), pam_sys::PamReturnCode> {
+        unsafe {
+            let rc = pam_sys::setcred(&mut *self.handle, pam_sys::PamFlag::ESTABLISH_CRED);
+            if rc != pam_sys::PamReturnCode::SUCCESS {
+                return Err(rc);
+            }
+
+            let rc = pam_sys::open_session(&mut *self.handle, pam_sys::PamFlag::NONE);
+            if rc != pam_sys::PamReturnCode::SUCCESS {
+                return Err(rc);
+            }
+
+            // Follow openSSH and call pam_setcred before and after open_session
+            let rc = pam_sys::setcred(&mut *self.handle, pam_sys::PamFlag::REINITIALIZE_CRED);
+            if rc != pam_sys::PamReturnCode::SUCCESS {
+                return Err(rc);
+            }
+        }
+        self.has_open_session = true;
+        Ok(())
+    }
+
+    fn get_env(&mut self) -> Vec<(String, String)> {
+        let mut vec = Vec::new();
+        unsafe {
+            let env_list = pam_sys::getenvlist(&mut *self.handle);
+            if !env_list.is_null() {
+                let mut idx = 0;
+                loop {
+                    let env_ptr = *env_list.offset(idx);
+                    if !env_ptr.is_null() {
+                        idx += 1;
+                        let env_str = std::ffi::CStr::from_ptr(env_ptr).to_string_lossy();
+                        let split: Vec<_> = env_str.splitn(2, '=').collect();
+                        if split.len() == 2 {
+                            vec.push((split[0].to_string(), split[1].to_string()));
+                        }
+                    } else {
+                        break;
+                    }
+                }
+                pam_sys::raw::pam_misc_drop_env(env_list as *mut *mut libc::c_char);
+            }
+        }
+        vec
+    }
+}
+
+impl Drop for PamSession {
+    fn drop(&mut self) {
+        unsafe {
+            if self.has_open_session {
+                pam_sys::close_session(&mut *self.handle, pam_sys::PamFlag::NONE);
+            }
+            let rc = pam_sys::setcred(&mut *self.handle, pam_sys::PamFlag::DELETE_CRED);
+            pam_sys::end(&mut *self.handle, rc);
+        }
+    }
+}
+
 fn run_daemon() {
     use users::os::unix::UserExt;
     let uid = users::get_current_uid();
@@ -1474,6 +1612,7 @@ fn run_daemon() {
             .arg(exe_path)
             .arg("--greeter")
             .env("XDG_RUNTIME_DIR", runtime_dir)
+            .env("LIBSEAT_BACKEND", "seatd")
             .stdout(std::process::Stdio::piped())
             .spawn()
             .expect("failed to spawn cage compositor wrapper. Is cage installed?");
@@ -1509,35 +1648,40 @@ fn run_daemon() {
             std::process::exit(0);
         }
 
+        if auth_success.is_none() {
+            // Sleep briefly to prevent high CPU usage if the greeter keeps crashing on startup
+            std::thread::sleep(std::time::Duration::from_millis(1000));
+        }
+
         if let Some((username, exec, is_wayland, password)) = auth_success {
             println!("[clear-display-manager] Launching user session Exec: '{}' (Wayland: {}) for user: '{}'", exec, is_wayland, username);
             
             let service = "clear-display-manager";
-            let mut auth = match pam::Authenticator::with_password(service) {
+            let mut auth = match PamSession::new(service, &username, &password) {
                 Ok(a) => a,
                 Err(_) => {
-                    match pam::Authenticator::with_password("login") {
+                    match PamSession::new("login", &username, &password) {
                         Ok(a) => a,
                         Err(e) => {
-                            eprintln!("[clear-display-manager] PAM Init Error in daemon: {}", e);
+                            eprintln!("[clear-display-manager] PAM Init Error in daemon: {:?}", e);
                             continue;
                         }
                     }
                 }
             };
 
-            auth.get_handler().set_credentials(username.clone(), password);
-
             if let Err(e) = auth.authenticate() {
-                eprintln!("[clear-display-manager] PAM Authentication failed in daemon: {}", e);
+                eprintln!("[clear-display-manager] PAM Authentication failed in daemon: {:?}", e);
                 continue;
             }
 
             if let Err(e) = auth.open_session() {
-                eprintln!("[clear-display-manager] PAM Session failed in daemon: {}", e);
+                eprintln!("[clear-display-manager] PAM Session failed in daemon: {:?}", e);
                 continue;
             }
 
+            let pam_env = auth.get_env();
+
             let user = match users::get_user_by_name(&username) {
                 Some(u) => u,
                 None => {
@@ -1576,6 +1720,7 @@ fn run_daemon() {
             let mut session_cmd = std::process::Command::new(&cmd_bin);
             session_cmd
                 .args(&cmd_args)
+                .envs(pam_env)
                 .current_dir(&home_dir)
                 .env("USER", &username)
                 .env("LOGNAME", &username)