]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
Add nss getpwnam to the himmelblau daemon
authorDavid Mulder <dmulder@samba.org>
Tue, 30 Jul 2024 20:40:09 +0000 (14:40 -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>
himmelblaud/src/himmelblaud.rs
himmelblaud/src/himmelblaud/himmelblaud_getpwnam.rs [new file with mode: 0644]
himmelblaud/src/main.rs

index 169f3b1f767c9ac2333e811af55e4f49a8f77694..5e2f2ee6221256f75ecb428a87a07ba1b3ac3e51 100644 (file)
@@ -18,7 +18,7 @@
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
-use crate::cache::{GroupCache, PrivateCache, UserCache};
+use crate::cache::{GroupCache, PrivateCache, UidCache, UserCache};
 use crate::himmelblaud::himmelblaud_pam_auth::AuthSession;
 use bytes::{BufMut, BytesMut};
 use dbg::{DBG_DEBUG, DBG_ERR, DBG_WARNING};
@@ -45,6 +45,7 @@ pub(crate) struct Resolver {
     graph: Graph,
     pcache: PrivateCache,
     user_cache: UserCache,
+    uid_cache: UidCache,
     group_cache: GroupCache,
     hsm: Mutex<BoxedDynTpm>,
     machine_key: MachineKey,
@@ -60,6 +61,7 @@ impl Resolver {
         graph: Graph,
         pcache: PrivateCache,
         user_cache: UserCache,
+        uid_cache: UidCache,
         group_cache: GroupCache,
         hsm: BoxedDynTpm,
         machine_key: MachineKey,
@@ -73,6 +75,7 @@ impl Resolver {
             graph,
             pcache,
             user_cache,
+            uid_cache,
             group_cache,
             hsm: Mutex::new(hsm),
             machine_key,
@@ -208,6 +211,9 @@ pub(crate) async fn handle_client(
                 }
             }
             Request::NssAccounts => resolver.getpwent().await?,
+            Request::NssAccountByName(account_id) => {
+                resolver.getpwnam(&account_id).await?
+            }
             _ => todo!(),
         };
         reqs.send(resp).await?;
@@ -220,4 +226,5 @@ pub(crate) async fn handle_client(
 }
 
 mod himmelblaud_getpwent;
+mod himmelblaud_getpwnam;
 mod himmelblaud_pam_auth;
diff --git a/himmelblaud/src/himmelblaud/himmelblaud_getpwnam.rs b/himmelblaud/src/himmelblaud/himmelblaud_getpwnam.rs
new file mode 100644 (file)
index 0000000..2389418
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   Himmelblau daemon implementation for nss getpwnam
+
+   Copyright (C) David Mulder 2024
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+use crate::himmelblaud::Resolver;
+use crate::utils::split_username;
+use dbg::{DBG_ERR, DBG_WARNING};
+use ntstatus_gen::*;
+use sock::{Passwd, Response};
+
+impl Resolver {
+    pub(crate) fn create_passwd_from_upn(
+        &mut self,
+        upn: &str,
+        gecos: &str,
+    ) -> Result<Passwd, Box<NTSTATUS>> {
+        let template_homedir = self
+            .lp
+            .template_homedir()
+            .map_err(|e| {
+                DBG_ERR!("{:?}", e);
+                Box::new(NT_STATUS_NOT_A_DIRECTORY)
+            })?
+            .ok_or_else(|| {
+                DBG_ERR!("Failed to discover template homedir. Is it set?");
+                Box::new(NT_STATUS_NOT_A_DIRECTORY)
+            })?;
+        let shell = self
+            .lp
+            .template_shell()
+            .map_err(|e| {
+                DBG_ERR!("{:?}", e);
+                Box::new(NT_STATUS_NOT_A_DIRECTORY)
+            })?
+            .ok_or_else(|| {
+                DBG_ERR!("Failed to discover template shell. Is it set?");
+                Box::new(NT_STATUS_NOT_A_DIRECTORY)
+            })?;
+        let uid = self
+            .idmap
+            .gen_to_unix(&self.tenant_id, &upn.to_lowercase())
+            .map_err(|e| {
+                DBG_ERR!("{:?}", e);
+                Box::new(NT_STATUS_INVALID_TOKEN)
+            })?;
+        // Store the calculated uid -> upn map in the cache
+        self.uid_cache.store(uid, &upn)?;
+        let (cn, domain) = match split_username(&upn) {
+            Ok(res) => res,
+            Err(e) => {
+                DBG_ERR!("Failed to parse user upn '{}': {:?}", upn, e);
+                return Err(Box::new(NT_STATUS_INVALID_USER_PRINCIPAL_NAME));
+            }
+        };
+        let homedir = template_homedir
+            .clone()
+            .replace("%D", &domain)
+            .replace("%U", &cn);
+        let passwd = Passwd {
+            name: upn.to_string(),
+            passwd: "x".to_string(),
+            uid,
+            gid: uid,
+            gecos: gecos.to_string(),
+            dir: homedir,
+            shell: shell.clone(),
+        };
+        return Ok(passwd);
+    }
+
+    pub(crate) async fn getpwnam(
+        &mut self,
+        account_id: &str,
+    ) -> Result<Response, Box<NTSTATUS>> {
+        // We first try to fetch the user from the cache, so that we
+        // get the gecos. Otherwise we can just create a passwd entry
+        // based on whether the upn exists in Entra ID.
+        let entry = match self.user_cache.fetch(account_id) {
+            Some(entry) => entry,
+            None => {
+                // Check if the user exists in Entra ID
+                let exists = match self
+                    .client
+                    .lock()
+                    .await
+                    .check_user_exists(&account_id)
+                    .await
+                {
+                    Ok(exists) => exists,
+                    Err(e) => {
+                        DBG_WARNING!("{:?}", e);
+                        return Ok(Response::NssAccount(None));
+                    }
+                };
+                if exists {
+                    return Ok(Response::NssAccount(Some(
+                        self.create_passwd_from_upn(account_id, "")?,
+                    )));
+                }
+                return Ok(Response::NssAccount(None));
+            }
+        };
+        return Ok(Response::NssAccount(Some(
+            self.create_passwd_from_upn(&entry.upn, &entry.name)?,
+        )));
+    }
+}
index 161191cd14924e6434c9d2a73b7858d6189e92c2..81cfeca493ff9214097b3d7ade5b8e27290aac66 100644 (file)
@@ -38,7 +38,7 @@ mod constants;
 use constants::DEFAULT_ODC_PROVIDER;
 mod cache;
 mod himmelblaud;
-use cache::{GroupCache, PrivateCache, UserCache};
+use cache::{GroupCache, PrivateCache, UidCache, UserCache};
 mod utils;
 
 #[tokio::main(flavor = "current_thread")]
@@ -175,6 +175,18 @@ async fn main() -> ExitCode {
             }
         };
 
+        let uid_cache_path = Path::new(&cache_dir)
+            .join("himmelblau_uid_map.tdb")
+            .display()
+            .to_string();
+        let uid_cache = match UidCache::new(&uid_cache_path) {
+            Ok(cache) => cache,
+            Err(e) => {
+                DBG_ERR!("Failed to open the himmelblau uid cache: {:?}", e);
+                return ExitCode::FAILURE;
+            }
+        };
+
         let group_cache_path = Path::new(&cache_dir)
             .join("himmelblau_groups.tdb")
             .display()
@@ -283,6 +295,7 @@ async fn main() -> ExitCode {
             graph,
             pcache,
             user_cache,
+            uid_cache,
             group_cache,
             hsm,
             machine_key,