diff --git a/himmelblaud/src/himmelblaud.rs b/himmelblaud/src/himmelblaud.rs index 169f3b1f767..5e2f2ee6221 100644 --- a/himmelblaud/src/himmelblaud.rs +++ b/himmelblaud/src/himmelblaud.rs @@ -18,7 +18,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -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, 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 index 00000000000..2389418b002 --- /dev/null +++ b/himmelblaud/src/himmelblaud/himmelblaud_getpwnam.rs @@ -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 . +*/ +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> { + 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> { + // 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)?, + ))); + } +} diff --git a/himmelblaud/src/main.rs b/himmelblaud/src/main.rs index 161191cd149..81cfeca493f 100644 --- a/himmelblaud/src/main.rs +++ b/himmelblaud/src/main.rs @@ -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,