From c85d31e9c2fbdd8d25e2d29db3d782fcf2b26d8c Mon Sep 17 00:00:00 2001
From: Dominik George <dominik.george@teckids.org>
Date: Fri, 14 May 2021 15:54:03 +0200
Subject: [PATCH] [WIP] Use thread-local objects for data store

---
 src/cache.rs |  43 +++++---------
 src/nss.rs   | 163 ++++++++++++++++++++++++++-------------------------
 src/pam.rs   |   8 ++-
 3 files changed, 103 insertions(+), 111 deletions(-)

diff --git a/src/cache.rs b/src/cache.rs
index 6a77b70..d6e5dad 100644
--- a/src/cache.rs
+++ b/src/cache.rs
@@ -21,6 +21,7 @@ use crate::BASE_NAME;
 use crate::unix::{Passwd, getpwnam_safe, getpwuid_safe};
 
 use lazy_static::lazy_static;
+use std::cell::RefCell;
 use std::sync::{Mutex, MutexGuard};
 
 use libc::{geteuid, seteuid, uid_t};
@@ -54,34 +55,16 @@ pub struct UserInfo<'a> {
     access_token: Option<BasicTokenResponse>
 }
 
-/// In-memory structure to hold global process state
-/// Needed because the PAM and NSS components might be used chained (if NSS
-/// resolution is necessary to complete PAM authentication), and we need to
-/// remember state between the calls.
-pub struct Cache<'a> {
-    /// The user currently calling the library
-    /// For NSS, this will be the process owner; for PAM, this will be the user
-    /// logging in (after successful authentication)
-    pub context_user: UserInfo<'a>
-}
-
-impl Cache<'_> {
-    pub fn new<'a>() -> Cache<'a> {
-        let euid = unsafe {
-            geteuid()
-        };
-        Cache {
-            context_user: UserInfo {
-                uid: None,
-                username: None,
-                passwd: None,
-                access_token: None
-            }
+impl <'a>UserInfo<'a> {
+    pub fn new() -> UserInfo<'a> {
+        Self {
+            uid: None,
+            username: None,
+            passwd: None,
+            access_token: None
         }
     }
-}
 
-impl <'a>UserInfo<'a> {
     /// Set the information of this user object to that of the process owner
     // FIXME Move to Cache, with a from_current_user generator method here
     pub fn set_current_user(&mut self) {
@@ -400,9 +383,11 @@ fn get_original_euid() -> uid_t {
     }
 }
 
-lazy_static! {
-    static ref CACHE: Mutex<Cache<'static>> = Mutex::new(Cache::new());
+thread_local! {
+    static CONTEXT_USER: RefCell<UserInfo<'static>> = RefCell::new(UserInfo::new());
 }
-pub fn get_cache() -> MutexGuard<'static, Cache<'static>> {
-    CACHE.lock().unwrap()
+pub fn with_context_user<R>(f: impl FnOnce(UserInfo<'static>) -> R) -> R {
+    CONTEXT_USER.with(|rc| {
+        f(rc.into_inner())
+    })
 }
diff --git a/src/nss.rs b/src/nss.rs
index dca4b9f..a152027 100644
--- a/src/nss.rs
+++ b/src/nss.rs
@@ -18,7 +18,7 @@ use crate::config::{
     get_optional,
 };
 use config::Config;
-use crate::cache::get_cache;
+use crate::cache::with_context_user;
 
 use crate::logging::setup_log;
 
@@ -53,9 +53,11 @@ fn nss_hook_prepare() -> Config {
     // Set the context user to the current user, but only if not already set
     // When doing PAM, we might get called back into by libc to do some NSS
     // lookup, and we want to keep the PAM login user context in that case
-    if !get_cache().context_user.is_initialized() {
-        get_cache().context_user.set_current_user();
-    }
+    with_context_user(|mut cu| {
+        if !cu.is_initialized() {
+            cu.set_current_user();
+        }
+    });
 
     return conf;
 }
@@ -67,99 +69,102 @@ impl PasswdHooks for OidcPasswd {
         let conf = nss_hook_prepare();
         info!("[NSS] passwd.get_all_entries called");
 
-        let mut cache = get_cache();
-        let user_token_res = cache.context_user.get_access_token();
-        // FIXME Implement caching of system token
-        let system_token_res = get_access_token_client(&conf, "nss", "", "");
-        let system_token_res = system_token_res.as_ref();
-        let token = match user_token_res {
-            Some(t) => t,
-            None => {
-                debug!("Could not find a user token to request NSS data; trying client credentials");
-                match system_token_res {
-                    Ok(ct) => ct,
-                    Err(e) => {
-                        error!("Failed to get access token with client credentials: {}", e);
-                        return Response::Unavail;
+        with_context_user(|mut cu| {
+            let user_token_res = cu.get_access_token();
+            // FIXME Implement caching of system token
+            let system_token_res = get_access_token_client(&conf, "nss", "", "");
+            let system_token_res = system_token_res;
+            let token = match user_token_res {
+                Some(t) => t,
+                None => {
+                    debug!("Could not find a user token to request NSS data; trying client credentials");
+                    match system_token_res.as_ref() {
+                        Ok(ct) => ct,
+                        Err(e) => {
+                            error!("Failed to get access token with client credentials: {}", e);
+                            return Response::Unavail;
+                        }
                     }
                 }
-            }
-        };
-
-        let data: Vec<PasswdHelper> = match get_data_jq(&conf, "nss", "passwd.list", "".to_string(), &token, true) {
-            Ok(d) => d,
-            Err(e) => {
-                error!("Could not load JSON data for passwd: {}", e);
-                return Response::Unavail;
-            }
-        };
-        Response::Success(data.into_iter().map(|p| p.0).collect())
+            };
+
+            let data: Vec<PasswdHelper> = match get_data_jq(&conf, "nss", "passwd.list", "".to_string(), &token, true) {
+                Ok(d) => d,
+                Err(e) => {
+                    error!("Could not load JSON data for passwd: {}", e);
+                    return Response::Unavail;
+                }
+            };
+            Response::Success(data.into_iter().map(|p| p.0).collect())
+        })
     }
 
     fn get_entry_by_uid(uid: libc::uid_t) -> Response<Passwd> {
         let conf = nss_hook_prepare();
         info!("[NSS] passwd.get_entry_by_uid called for {}", uid);
 
-        let mut cache = get_cache();
-        let user_token_res = cache.context_user.get_access_token();
-        // FIXME Implement caching of system token
-        let system_token_res = get_access_token_client(&conf, "nss", "", "");
-        let system_token_res = system_token_res.as_ref();
-        let token = match user_token_res {
-            Some(t) => t,
-            None => {
-                debug!("Could not find a user token to request NSS data; trying client credentials");
-                match system_token_res {
-                    Ok(ct) => ct,
-                    Err(e) => {
-                        error!("Failed to get access token with client credentials: {}", e);
-                        return Response::Unavail;
+        with_context_user(|mut cu| {
+            let user_token_res = cu.get_access_token();
+            // FIXME Implement caching of system token
+            let system_token_res = get_access_token_client(&conf, "nss", "", "");
+            let system_token_res = system_token_res;
+            let token = match user_token_res {
+                Some(t) => t,
+                None => {
+                    debug!("Could not find a user token to request NSS data; trying client credentials");
+                    match system_token_res.as_ref() {
+                        Ok(ct) => ct,
+                        Err(e) => {
+                            error!("Failed to get access token with client credentials: {}", e);
+                            return Response::Unavail;
+                        }
                     }
                 }
-            }
-        };
-
-        let data: Passwd = match get_data_jq(&conf, "nss", "passwd.by_uid", uid, &token, false).map(|PasswdHelper(p)| p) {
-            Ok(d) => d,
-            Err(e) => {
-                error!("Could not load JSON data for passwd: {}", e);
-                return Response::NotFound;
-            }
-        };
-        Response::Success(data)
+            };
+
+            let data: Passwd = match get_data_jq(&conf, "nss", "passwd.by_uid", uid, &token, false).map(|PasswdHelper(p)| p) {
+                Ok(d) => d,
+                Err(e) => {
+                    error!("Could not load JSON data for passwd: {}", e);
+                    return Response::NotFound;
+                }
+            };
+            Response::Success(data)
+        })
     }
 
     fn get_entry_by_name(name: String) -> Response<Passwd> {
         let conf = nss_hook_prepare();
         info!("[NSS] passwd.get_entry_by_name called for {}", name);
 
-        let mut cache = get_cache();
-        let user_token_res = cache.context_user.get_access_token();
-        // FIXME Implement caching of system token
-        let system_token_res = get_access_token_client(&conf, "nss", "", "");
-        let system_token_res = system_token_res.as_ref();
-        let token = match user_token_res {
-            Some(t) => t,
-            None => {
-                debug!("Could not find a user token to request NSS data; trying client credentials");
-                match system_token_res {
-                    Ok(ct) => ct,
-                    Err(e) => {
-                        error!("Failed to get access token with client credentials: {}", e);
-                        return Response::Unavail;
+        with_context_user(|mut cu| {
+            let user_token_res = cu.get_access_token();
+            // FIXME Implement caching of system token
+            let system_token_res = get_access_token_client(&conf, "nss", "", "");
+            let system_token_res = system_token_res;
+            let token = match user_token_res {
+                Some(t) => t,
+                None => {
+                    debug!("Could not find a user token to request NSS data; trying client credentials");
+                    match system_token_res.as_ref() {
+                        Ok(ct) => ct,
+                        Err(e) => {
+                            error!("Failed to get access token with client credentials: {}", e);
+                            return Response::Unavail;
+                        }
                     }
                 }
-            }
-        };
-
-        let data: Passwd = match get_data_jq(&conf, "nss", "passwd.by_name", name, &token, false).map(|PasswdHelper(p)| p) {
-            Ok(d) => d,
-            Err(e) => {
-                error!("Could not load JSON data for passwd: {}", e);
-                return Response::NotFound;
-            }
-        };
-        Response::Success(data)
+            };
+
+            let data: Passwd = match get_data_jq(&conf, "nss", "passwd.by_name", &name, &token, false).map(|PasswdHelper(p)| p) {
+                Ok(d) => d,
+                Err(e) => {
+                    error!("Could not load JSON data for passwd: {}", e);
+                    return Response::NotFound;
+                }
+            };
+            Response::Success(data)
+        })
     }
 }
 
diff --git a/src/pam.rs b/src/pam.rs
index 02b26a7..b4f1126 100644
--- a/src/pam.rs
+++ b/src/pam.rs
@@ -24,7 +24,7 @@ use crate::oauth::get_access_token_password;
 
 use crate::logging::setup_log;
 
-use crate::cache::{get_cache, set_is_getpwnam_safe};
+use crate::cache::{set_is_getpwnam_safe, with_context_user};
 
 use pamsm::{PamServiceModule, Pam, PamFlag, PamError, PamLibExt};
 
@@ -91,8 +91,10 @@ impl PamServiceModule for PamOidc {
                 Ok(t) => {
                     info!("Authenticated {} using Resource Owner Password Grant", username);
                     set_is_getpwnam_safe(false);
-                    get_cache().context_user.set_username(username.to_string());
-                    get_cache().context_user.set_access_token(t);
+                    with_context_user(|mut cu| {
+                        cu.set_username(username.to_string());
+                        cu.set_access_token(t);
+                    });
                     set_is_getpwnam_safe(true);
                     return PamError::SUCCESS;
                 },
-- 
GitLab