diff --git a/docs-xml/smbdotconf/himmelblaud/himmelblaudhelloenabled.xml b/docs-xml/smbdotconf/himmelblaud/himmelblaudhelloenabled.xml new file mode 100644 index 00000000000..b148362862f --- /dev/null +++ b/docs-xml/smbdotconf/himmelblaud/himmelblaudhelloenabled.xml @@ -0,0 +1,18 @@ + + + + This parameter controls Hello enrollment and authentication to + Azure Entra ID. By default, it is disabled to prevent security risks, + such as on hosts exposing the SSH port. Administrators should enable + this setting only when Hello enrollment is appropriate for their + environment. + + + + +no +yes + diff --git a/docs-xml/smbdotconf/himmelblaud/himmelblaudsfafallback.xml b/docs-xml/smbdotconf/himmelblaud/himmelblaudsfafallback.xml new file mode 100644 index 00000000000..1b30d0a87c9 --- /dev/null +++ b/docs-xml/smbdotconf/himmelblaud/himmelblaudsfafallback.xml @@ -0,0 +1,17 @@ + + + + This parameter is designed to control whether Himmelblaud should fallback to + Single Factor Authentication (SFA) if Multi-Factor Authentication (MFA) isn't + available. This normally is possible during a short window during which MFA + enrollment is available to a new user. + + + + +no +yes + diff --git a/himmelblaud/Cargo.toml b/himmelblaud/Cargo.toml index 69197cd5ff3..bdf487d30da 100644 --- a/himmelblaud/Cargo.toml +++ b/himmelblaud/Cargo.toml @@ -17,9 +17,11 @@ dbg = { workspace = true } [workspace] members = [ "chelps", "dbg", "ntstatus_gen", + "param", ] [workspace.dependencies] +param = { path = "param" } dbg = { path = "dbg" } chelps = { path = "chelps" } ntstatus_gen = { path = "ntstatus_gen" } diff --git a/himmelblaud/param/Cargo.toml b/himmelblaud/param/Cargo.toml new file mode 100644 index 00000000000..e90e500d7f5 --- /dev/null +++ b/himmelblaud/param/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "param" +edition.workspace = true +license.workspace = true +homepage.workspace = true +version.workspace = true + +[dependencies] +chelps = { workspace = true } +dbg.workspace = true +ntstatus_gen.workspace = true +paste = "1.0.15" + +[build-dependencies] +bindgen = "0.69.4" diff --git a/himmelblaud/param/build.rs b/himmelblaud/param/build.rs new file mode 100644 index 00000000000..d2fa9df8753 --- /dev/null +++ b/himmelblaud/param/build.rs @@ -0,0 +1,48 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + let bindings = bindgen::Builder::default() + .blocklist_function("qgcvt") + .blocklist_function("qgcvt_r") + .blocklist_function("qfcvt") + .blocklist_function("qfcvt_r") + .blocklist_function("qecvt") + .blocklist_function("qecvt_r") + .blocklist_function("strtold") + .clang_arg("-Dbool=int") + .clang_arg("-Doffset_t=loff_t") + .clang_arg("-I../../bin/default") + .clang_arg("-includestdint.h") + .header("../../lib/param/param.h") + .header("../../lib/param/loadparm.h") + .header("../../source3/param/loadparm.h") + .header("../../bin/default/lib/param/param_functions.h") + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .generate() + .expect("Unable to generate bindings"); + println!( + "cargo:rerun-if-changed=../../bin/default/lib/param/param_functions.h" + ); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); + + let mut src_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + src_dir.push("../../bin/default/source3"); + println!( + "cargo:rustc-link-search=native={}", + src_dir.to_str().unwrap() + ); + println!("cargo:rustc-link-lib=smbconf"); + + let mut src_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + src_dir.push("../../bin/default/lib/param"); + println!("cargo:rustc-link-lib=samba-hostconfig-private-samba"); + println!( + "cargo:rustc-link-search=native={}", + src_dir.to_str().unwrap() + ); +} diff --git a/himmelblaud/param/src/lib.rs b/himmelblaud/param/src/lib.rs new file mode 100644 index 00000000000..7b4bcaf5325 --- /dev/null +++ b/himmelblaud/param/src/lib.rs @@ -0,0 +1,208 @@ +/* + Unix SMB/CIFS implementation. + + Parameter loading functions + + 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 chelps::{string_free, wrap_c_char, wrap_string}; +use dbg::{DBG_ERR, DBG_INFO, DBG_WARNING}; +use ntstatus_gen::NT_STATUS_UNSUCCESSFUL; +use std::error::Error; +use std::ffi::c_void; +use std::sync::{Arc, Mutex}; + +mod ffi { + #![allow(non_upper_case_globals)] + #![allow(non_camel_case_types)] + #![allow(non_snake_case)] + #![allow(dead_code)] + #![allow(clippy::upper_case_acronyms)] + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} + +pub struct LoadParm { + lp: Arc>, +} + +macro_rules! lpcfg_str { + ($var:ident) => { + paste::item! { + pub fn $var (&self) -> Result, Box> { + let lp = self.lp.lock()?; + let val = unsafe { ffi::[< lpcfg_ $var >] (*lp) } ; + unsafe { Ok(wrap_c_char(val)) } + } + } + }; +} + +macro_rules! lpcfg_i32 { + ($var:ident) => { + paste::item! { + pub fn $var (&self) -> Result> { + let lp = self.lp.lock()?; + unsafe { Ok(ffi::[< lpcfg_ $var >] (*lp)) } + } + } + }; +} + +macro_rules! lpcfg_bool { + ($var:ident) => { + paste::item! { + pub fn $var (&self) -> Result> { + let lp = self.lp.lock()?; + unsafe { Ok(ffi::[< lpcfg_ $var >] (*lp) != 0) } + } + } + }; +} + +impl LoadParm { + pub fn new(configfile: Option<&str>) -> Result> { + let lp = unsafe { + match configfile { + Some(configfile) => { + let configfile_cstr = wrap_string(configfile); + let lp = ffi::loadparm_init_global(0); + if ffi::lpcfg_load(lp, configfile_cstr) != 1 { + return Err(Box::new(NT_STATUS_UNSUCCESSFUL)); + } + string_free(configfile_cstr); + lp + } + None => ffi::loadparm_init_global(1), + } + }; + Ok(LoadParm { + lp: Arc::new(Mutex::new(lp)), + }) + } + + pub fn private_path( + &self, + name: &str, + ) -> Result, Box> { + let lp = self.lp.lock()?; + let path = unsafe { + let name_cstr = wrap_string(name); + let path = + ffi::lpcfg_private_path(*lp as *mut c_void, *lp, name_cstr); + string_free(name_cstr); + path + }; + unsafe { Ok(wrap_c_char(path)) } + } + + pub fn logfile(&self) -> Result, Box> { + let lp = self.lp.lock()?; + let logfile = unsafe { + let lp_sub = ffi::lpcfg_noop_substitution(); + ffi::lpcfg_logfile(*lp, lp_sub, *lp as *mut c_void) + }; + unsafe { Ok(wrap_c_char(logfile)) } + } + + pub fn idmap_range( + &self, + domain_name: &str, + ) -> Result<(u32, u32), Box> { + if let Ok(Some(backend)) = self.idmap_backend(domain_name) { + if backend != "upn" { + DBG_ERR!("Backend '{}' is not supported for Entra ID", backend); + return Err(Box::new(NT_STATUS_UNSUCCESSFUL)); + } + } else { + DBG_WARNING!( + "No idmap backend configured for domain '{}'", + domain_name + ); + DBG_INFO!("Falling back to default idmap configuration"); + return self.idmap_default_range(); + } + let mut low: u32 = 0; + let mut high: u32 = 0; + let res = unsafe { + let domain_name_cstr = wrap_string(domain_name); + let res = + ffi::lp_idmap_range(domain_name_cstr, &mut low, &mut high); + string_free(domain_name_cstr); + res + }; + if res == 0 { + return Err(Box::new(NT_STATUS_UNSUCCESSFUL)); + } + Ok((low, high)) + } + + pub fn idmap_backend( + &self, + domain_name: &str, + ) -> Result, Box> { + let backend = unsafe { + let domain_name_cstr = wrap_string(domain_name); + let backend = ffi::lp_idmap_backend(domain_name_cstr); + string_free(domain_name_cstr); + backend + }; + unsafe { Ok(wrap_c_char(backend)) } + } + + pub fn idmap_default_range( + &self, + ) -> Result<(u32, u32), Box> { + if let Ok(Some(backend)) = self.idmap_default_backend() { + if backend != "upn" { + DBG_ERR!( + "Default backend '{}' is not supported for Entra ID", + backend + ); + return Err(Box::new(NT_STATUS_UNSUCCESSFUL)); + } + } else { + DBG_ERR!("No default idmap backend configured."); + return Err(Box::new(NT_STATUS_UNSUCCESSFUL)); + } + let mut low: u32 = 0; + let mut high: u32 = 0; + let res = unsafe { ffi::lp_idmap_default_range(&mut low, &mut high) }; + if res == 0 { + return Err(Box::new(NT_STATUS_UNSUCCESSFUL)); + } + Ok((low, high)) + } + + pub fn idmap_default_backend( + &self, + ) -> Result, Box> { + let backend = unsafe { ffi::lp_idmap_default_backend() }; + unsafe { Ok(wrap_c_char(backend)) } + } + + lpcfg_str!(realm); + lpcfg_str!(winbindd_socket_directory); + lpcfg_i32!(winbind_request_timeout); + lpcfg_bool!(himmelblaud_sfa_fallback); + lpcfg_bool!(himmelblaud_hello_enabled); + lpcfg_str!(cache_directory); + lpcfg_str!(template_homedir); + lpcfg_str!(template_shell); +} + +unsafe impl Send for LoadParm {} +unsafe impl Sync for LoadParm {} diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c index 047c59d3c6b..01c8df0ed70 100644 --- a/lib/param/loadparm.c +++ b/lib/param/loadparm.c @@ -3165,6 +3165,14 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx) "acl claims evaluation", "AD DC only"); + /* Set the default Himmelblaud globals */ + lpcfg_do_global_parameter(lp_ctx, + "himmelblaud hello enabled", + "false"); + lpcfg_do_global_parameter(lp_ctx, + "himmelblaud sfa fallback", + "false"); + for (i = 0; parm_table[i].label; i++) { if (!(lp_ctx->flags[i] & FLAG_CMDLINE)) { lp_ctx->flags[i] |= FLAG_DEFAULT; diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index 557567d927f..ae23d22f401 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -1004,6 +1004,10 @@ void loadparm_s3_init_globals(struct loadparm_context *lp_ctx, Globals.acl_claims_evaluation = ACL_CLAIMS_EVALUATION_AD_DC_ONLY; + /* Set the default Himmelblaud globals */ + Globals.himmelblaud_hello_enabled = false; + Globals.himmelblaud_sfa_fallback = false; + /* Now put back the settings that were set with lp_set_cmdline() */ apply_lp_set_cmdline(); }