diff --git a/Cargo.toml b/Cargo.toml index eac7ed2d..7111e9ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "src/users", "src/idmap", "src/file_permissions", + "src/broker", ] resolver = "2" @@ -37,12 +38,12 @@ tracing-subscriber = "^0.3.17" tracing = "^0.1.37" himmelblau_unix_common = { path = "src/common" } kanidm_unix_common = { path = "src/glue" } -libhimmelblau = { version = "0.2.9" } +libhimmelblau = { version = "0.3.0" } clap = { version = "^4.5", features = ["derive", "env"] } clap_complete = "^4.4.1" reqwest = { version = "^0.12.2", features = ["json"] } anyhow = "^1.0.71" -tokio = { version = "^1.28.1", features = ["rt", "macros", "sync", "time", "net", "io-util", "signal"] } +tokio = { version = "^1.28.1", features = ["rt", "macros", "sync", "time", "net", "io-util", "signal", "rt-multi-thread"] } tokio-util = { version = "^0.7.8", features = ["codec"] } async-trait = "^0.1.72" pem = "^3.0.2" @@ -51,6 +52,7 @@ os-release = "^0.1.0" jsonwebtoken = "^9.2.0" zeroize = "^1.7.0" idmap = { path = "src/idmap" } +identity_dbus_broker = "0.1.0" # Kanidm deps argon2 = { version = "0.5.2", features = ["alloc"] } diff --git a/platform/debian/com.microsoft.identity.broker.service b/platform/debian/com.microsoft.identity.broker.service new file mode 100644 index 00000000..c0f595f8 --- /dev/null +++ b/platform/debian/com.microsoft.identity.broker.service @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=com.microsoft.identity.broker1 +Exec=/usr/sbin/broker diff --git a/platform/debian/himmelblaud.service b/platform/debian/himmelblaud.service index 04a30e5c..2ff9e0c4 100644 --- a/platform/debian/himmelblaud.service +++ b/platform/debian/himmelblaud.service @@ -6,13 +6,14 @@ Description=Himmelblau Authentication Daemon After=chronyd.service ntpd.service network-online.target [Service] +BusName=org.samba.himmelblau DynamicUser=yes UMask=0027 CacheDirectory=himmelblaud # /var/cache/himmelblaud RuntimeDirectory=himmelblaud # /run/himmelblaud StateDirectory=himmelblaud # /var/lib/himmelblaud -Type=simple +Type=dbus ExecStart=/usr/sbin/himmelblaud # Implied by dynamic user. diff --git a/platform/debian/org.samba.himmelblau.conf b/platform/debian/org.samba.himmelblau.conf new file mode 100644 index 00000000..d508affa --- /dev/null +++ b/platform/debian/org.samba.himmelblau.conf @@ -0,0 +1,8 @@ + + + + + + + diff --git a/platform/opensuse/com.microsoft.identity.broker.service b/platform/opensuse/com.microsoft.identity.broker.service new file mode 100644 index 00000000..c0f595f8 --- /dev/null +++ b/platform/opensuse/com.microsoft.identity.broker.service @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=com.microsoft.identity.broker1 +Exec=/usr/sbin/broker diff --git a/platform/opensuse/himmelblaud.service b/platform/opensuse/himmelblaud.service index e9d25821..9402ae49 100644 --- a/platform/opensuse/himmelblaud.service +++ b/platform/opensuse/himmelblaud.service @@ -6,13 +6,14 @@ Description=Himmelblau Authentication Daemon After=chronyd.service ntpd.service network-online.target suspend.target [Service] +BusName=org.samba.himmelblau DynamicUser=yes UMask=0027 CacheDirectory=himmelblaud # /var/cache/himmelblaud RuntimeDirectory=himmelblaud # /run/himmelblaud StateDirectory=himmelblaud # /var/lib/himmelblaud -Type=simple +Type=dbus ExecStart=/usr/sbin/himmelblaud # Implied by dynamic user. diff --git a/platform/opensuse/org.samba.himmelblau.conf b/platform/opensuse/org.samba.himmelblau.conf new file mode 100644 index 00000000..d508affa --- /dev/null +++ b/platform/opensuse/org.samba.himmelblau.conf @@ -0,0 +1,8 @@ + + + + + + + diff --git a/src/broker/Cargo.toml b/src/broker/Cargo.toml new file mode 100644 index 00000000..1d1dde1e --- /dev/null +++ b/src/broker/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "broker" +version.workspace = true +authors.workspace = true +rust-version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +dbus = "0.9.7" +identity_dbus_broker.workspace = true +tokio.workspace = true diff --git a/src/broker/src/main.rs b/src/broker/src/main.rs new file mode 100644 index 00000000..f33f0440 --- /dev/null +++ b/src/broker/src/main.rs @@ -0,0 +1,23 @@ +/* + Unix Azure Entra ID implementation + 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 identity_dbus_broker::himmelblau_session_broker_serve; + +#[tokio::main] +async fn main() -> Result<(), dbus::MethodErr> { + himmelblau_session_broker_serve().await +} diff --git a/src/common/src/idprovider/himmelblau.rs b/src/common/src/idprovider/himmelblau.rs index c944c9c6..114ac2b8 100644 --- a/src/common/src/idprovider/himmelblau.rs +++ b/src/common/src/idprovider/himmelblau.rs @@ -149,6 +149,61 @@ impl IdProvider for HimmelblauMultiProvider { Ok(()) } + async fn unix_user_access( + &self, + id: &Id, + scopes: Vec, + old_token: Option<&UserToken>, + tpm: &mut tpm::BoxedDynTpm, + machine_key: &tpm::MachineKey, + ) -> Result { + let account_id = match old_token { + Some(token) => token.spn.clone(), + None => id.to_string().clone(), + }; + match split_username(&account_id) { + Some((_sam, domain)) => { + let providers = self.providers.read().await; + match providers.get(domain) { + Some(provider) => { + provider + .unix_user_access(id, scopes, old_token, tpm, machine_key) + .await + } + None => Err(IdpError::NotFound), + } + } + None => Err(IdpError::NotFound), + } + } + + async fn unix_user_prt_cookie( + &self, + id: &Id, + old_token: Option<&UserToken>, + tpm: &mut tpm::BoxedDynTpm, + machine_key: &tpm::MachineKey, + ) -> Result { + let account_id = match old_token { + Some(token) => token.spn.clone(), + None => id.to_string().clone(), + }; + match split_username(&account_id) { + Some((_sam, domain)) => { + let providers = self.providers.read().await; + match providers.get(domain) { + Some(provider) => { + provider + .unix_user_prt_cookie(id, old_token, tpm, machine_key) + .await + } + None => Err(IdpError::NotFound), + } + } + None => Err(IdpError::NotFound), + } + } + async fn unix_user_get( &self, id: &Id, @@ -491,6 +546,61 @@ impl IdProvider for HimmelblauProvider { } } + async fn unix_user_access( + &self, + id: &Id, + scopes: Vec, + old_token: Option<&UserToken>, + tpm: &mut tpm::BoxedDynTpm, + machine_key: &tpm::MachineKey, + ) -> Result { + /* Use the prt mem cache to refresh the user token */ + let account_id = match old_token { + Some(token) => token.spn.clone(), + None => id.to_string().clone(), + }; + let prt = self.refresh_cache.refresh_token(&account_id).await?; + self.client + .write() + .await + .exchange_prt_for_access_token( + &prt, + scopes.iter().map(|s| s.as_ref()).collect(), + None, + tpm, + machine_key, + ) + .await + .map_err(|e| { + error!("{:?}", e); + IdpError::BadRequest + }) + } + + async fn unix_user_prt_cookie( + &self, + id: &Id, + old_token: Option<&UserToken>, + tpm: &mut tpm::BoxedDynTpm, + machine_key: &tpm::MachineKey, + ) -> Result { + /* Use the prt mem cache to refresh the user token */ + let account_id = match old_token { + Some(token) => token.spn.clone(), + None => id.to_string().clone(), + }; + let prt = self.refresh_cache.refresh_token(&account_id).await?; + self.client + .write() + .await + .acquire_prt_sso_cookie(&prt, tpm, machine_key) + .await + .map_err(|e| { + error!("Failed to request prt cookie: {:?}", e); + IdpError::BadRequest + }) + } + async fn unix_user_get( &self, id: &Id, @@ -555,7 +665,10 @@ impl IdProvider for HimmelblauProvider { displayname: "".to_string(), shell: Some(config.get_shell(Some(&self.domain))), groups, - sshkeys: vec![], + tenant_id: Uuid::parse_str(&self.tenant_id).map_err(|e| { + error!("{:?}", e); + IdpError::BadRequest + })?, valid: true, }); } else { @@ -1344,7 +1457,6 @@ impl HimmelblauProvider { groups = vec![]; } }; - let sshkeys: Vec = vec![]; let valid = true; let idmap = self.idmap.read().await; let gidnumber = match config.get_id_attr_map() { @@ -1376,7 +1488,10 @@ impl HimmelblauProvider { displayname: value.id_token.name.clone(), shell: Some(config.get_shell(Some(&self.domain))), groups, - sshkeys, + tenant_id: Uuid::parse_str(&self.tenant_id).map_err(|e| { + error!("{:?}", e); + IdpError::BadRequest + })?, valid, }) } diff --git a/src/common/src/idprovider/interface.rs b/src/common/src/idprovider/interface.rs index 81ed9e98..c2b43f9d 100644 --- a/src/common/src/idprovider/interface.rs +++ b/src/common/src/idprovider/interface.rs @@ -7,6 +7,7 @@ use crate::db::KeyStoreTxn; use crate::unix_proto::{DeviceAuthorizationResponse, PamAuthRequest, PamAuthResponse}; use async_trait::async_trait; +use himmelblau::auth::UserToken as UnixUserToken; use serde::{Deserialize, Serialize}; use tokio::sync::broadcast; use uuid::Uuid; @@ -60,8 +61,7 @@ pub struct UserToken { pub displayname: String, pub shell: Option, pub groups: Vec, - // Could there be a better type here? - pub sshkeys: Vec, + pub tenant_id: Uuid, // Defaults to false. pub valid: bool, } @@ -176,6 +176,23 @@ pub trait IdProvider { _machine_key: &tpm::MachineKey, ) -> Result; + async fn unix_user_access( + &self, + _id: &Id, + _scopes: Vec, + _token: Option<&UserToken>, + _tpm: &mut tpm::BoxedDynTpm, + _machine_key: &tpm::MachineKey, + ) -> Result; + + async fn unix_user_prt_cookie( + &self, + _id: &Id, + _token: Option<&UserToken>, + _tpm: &mut tpm::BoxedDynTpm, + _machine_key: &tpm::MachineKey, + ) -> Result; + async fn unix_user_online_auth_init( &self, _account_id: &str, diff --git a/src/common/src/resolver.rs b/src/common/src/resolver.rs index 211f369b..49d18296 100644 --- a/src/common/src/resolver.rs +++ b/src/common/src/resolver.rs @@ -37,6 +37,8 @@ use kanidm_hsm_crypto::{BoxedDynTpm, HmacKey, MachineKey, Tpm}; use tokio::sync::broadcast; +use himmelblau::auth::UserToken as UnixUserToken; + const NXCACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(128) }; #[derive(Debug, Clone)] @@ -604,7 +606,76 @@ where } } - async fn get_usertoken(&self, account_id: Id) -> Result, ()> { + pub async fn get_user_accesstoken( + &self, + account_id: Id, + scopes: Vec, + ) -> Option { + let token = match self.get_usertoken(account_id.clone()).await { + Ok(Some(token)) => token, + _ => { + error!("Failed to fetch unix user token during access token request!"); + return None; + } + }; + + let mut hsm_lock = self.hsm.lock().await; + + let user_get_result = self + .client + .unix_user_access( + &account_id, + scopes, + Some(&token), + hsm_lock.deref_mut(), + &self.machine_key, + ) + .await; + + drop(hsm_lock); + + match user_get_result { + Ok(token) => Some(token), + Err(e) => { + error!("Failed to fetch access token: {:?}", e); + None + } + } + } + + pub async fn get_user_prt_cookie(&self, account_id: Id) -> Option { + let token = match self.get_usertoken(account_id.clone()).await { + Ok(Some(token)) => token, + _ => { + error!("Failed to fetch unix user token during access token request!"); + return None; + } + }; + + let mut hsm_lock = self.hsm.lock().await; + + let cookie = self + .client + .unix_user_prt_cookie( + &account_id, + Some(&token), + hsm_lock.deref_mut(), + &self.machine_key, + ) + .await; + + drop(hsm_lock); + + match cookie { + Ok(cookie) => Some(cookie), + Err(e) => { + error!("Failed to fetch prt sso cookie: {:?}", e); + None + } + } + } + + pub async fn get_usertoken(&self, account_id: Id) -> Result, ()> { debug!("get_usertoken"); // get the item from the cache let (expired, item) = self.get_cached_usertoken(&account_id).await.map_err(|e| { @@ -714,21 +785,6 @@ where .collect() } - // Get ssh keys for an account id - pub async fn get_sshkeys(&self, account_id: &str) -> Result, ()> { - let token = self.get_usertoken(Id::Name(account_id.to_string())).await?; - Ok(token - .map(|t| { - // Only return keys if the account is valid - if t.valid { - t.sshkeys - } else { - Vec::with_capacity(0) - } - }) - .unwrap_or_else(|| Vec::with_capacity(0))) - } - #[inline(always)] fn token_homedirectory_alias(&self, token: &UserToken) -> Option { self.home_alias.map(|t| match t { diff --git a/src/common/src/unix_proto.rs b/src/common/src/unix_proto.rs index 2042a2e6..a74d861b 100644 --- a/src/common/src/unix_proto.rs +++ b/src/common/src/unix_proto.rs @@ -77,7 +77,6 @@ pub enum PamAuthRequest { #[derive(Serialize, Deserialize, Debug)] pub enum ClientRequest { - SshKey(String), NssAccounts, NssAccountByUid(u32), NssAccountByName(String), @@ -97,7 +96,6 @@ impl ClientRequest { /// Get a safe display version of the request, without credentials. pub fn as_safe_string(&self) -> String { match self { - ClientRequest::SshKey(id) => format!("SshKey({})", id), ClientRequest::NssAccounts => "NssAccounts".to_string(), ClientRequest::NssAccountByUid(id) => format!("NssAccountByUid({})", id), ClientRequest::NssAccountByName(id) => format!("NssAccountByName({})", id), @@ -119,7 +117,6 @@ impl ClientRequest { #[derive(Serialize, Deserialize, Debug)] pub enum ClientResponse { - SshKeys(Vec), NssAccounts(Vec), NssAccount(Option), NssGroups(Vec), @@ -162,8 +159,4 @@ fn test_clientrequest_as_safe_string() { ClientRequest::NssAccounts.as_safe_string(), "NssAccounts".to_string() ); - assert_eq!( - ClientRequest::SshKey("cheese".to_string()).as_safe_string(), - format!("SshKey({})", "cheese") - ); } diff --git a/src/daemon/Cargo.toml b/src/daemon/Cargo.toml index 44e95a18..f1579061 100644 --- a/src/daemon/Cargo.toml +++ b/src/daemon/Cargo.toml @@ -27,7 +27,7 @@ tracing-subscriber = { workspace = true } clap = { workspace = true } bytes = "^1.3.0" serde = { workspace = true } -serde_json = { workspace = true } +serde_json.workspace = true futures = "^0.3.28" systemd-journal-logger = "^2.1.1" users = "^0.11.0" @@ -39,3 +39,6 @@ csv = { workspace = true } notify-debouncer-full = { workspace = true } kanidm-hsm-crypto = { workspace = true } kanidm_lib_file_permissions.workspace = true +identity_dbus_broker.workspace = true +dbus = "0.9.7" +base64.workspace = true diff --git a/src/daemon/src/broker.rs b/src/daemon/src/broker.rs new file mode 100644 index 00000000..c9a8afb3 --- /dev/null +++ b/src/daemon/src/broker.rs @@ -0,0 +1,188 @@ +use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use base64::Engine; +use himmelblau_unix_common::idprovider::himmelblau::HimmelblauMultiProvider; +use himmelblau_unix_common::idprovider::interface::Id; +use himmelblau_unix_common::resolver::Resolver; +use identity_dbus_broker::HimmelblauBroker; +use libc::uid_t; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; +use tokio::runtime::Runtime; + +#[derive(Serialize, Deserialize, Debug)] +struct AccountReq { + username: String, +} + +#[derive(Serialize, Deserialize, Debug)] +struct AuthParametersReq { + #[serde(rename = "requestedScopes")] + requested_scopes: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +struct TokenReq { + account: AccountReq, + #[serde(rename = "authParameters")] + auth_parameters: AuthParametersReq, +} + +#[derive(Serialize, Deserialize, Debug)] +struct SsoCookieReq { + username: String, +} + +pub(crate) struct Broker { + pub(crate) cachelayer: Arc>, +} + +impl HimmelblauBroker for Broker { + fn acquire_token_interactively( + &mut self, + protocol_version: String, + correlation_id: String, + request_json: String, + uid: uid_t, + ) -> Result { + self.acquire_token_silently(protocol_version, correlation_id, request_json, uid) + } + + fn acquire_token_silently( + &mut self, + _protocol_version: String, + _correlation_id: String, + request_json: String, + uid: uid_t, + ) -> Result { + let rt = Runtime::new().map_err(|e| dbus::MethodErr::failed(&format!("{:?}", e)))?; + // Double check the user is making a request for their own account + let user = rt + .block_on(self.cachelayer.get_usertoken(Id::Gid(uid))) + .map_err(|e| dbus::MethodErr::failed(&format!("Unable to load account: {:?}", e)))? + .ok_or(dbus::MethodErr::failed("Unable to find account"))?; + let request: TokenReq = serde_json::from_str(&request_json) + .map_err(|e| dbus::MethodErr::failed(&format!("{:?}", e)))?; + if request.account.username.to_lowercase() != user.spn.to_lowercase() { + return Err(dbus::MethodErr::failed("Invalid request for user!")); + } + let scopes = request.auth_parameters.requested_scopes; + let token = rt + .block_on( + self.cachelayer + .get_user_accesstoken(Id::Name(user.spn), scopes), + ) + .ok_or(dbus::MethodErr::failed("Failed to authenticate user"))?; + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| dbus::MethodErr::failed(&format!("{:?}", e)))?; + let res = json!({ + "brokerTokenResponse": { + "accessToken": token.access_token.clone() + .ok_or(dbus::MethodErr::failed("Failed to fetch access token"))?, + "accessTokenType": token.token_type, + "clientInfo": URL_SAFE_NO_PAD.encode(json!(&token.client_info).to_string()), + "expiresOn": (token.expires_in as u128) + now.as_millis(), + "extendedExpiresOn": (token.ext_expires_in as u64) + now.as_secs(), + "grantedScopes": token.scope.clone() + .ok_or(dbus::MethodErr::failed("Failed to fetch scopes"))?, + "idToken": URL_SAFE_NO_PAD.encode(json!(&token.id_token).to_string()), + } + }); + Ok(res.to_string()) + } + + fn get_accounts( + &mut self, + _protocol_version: String, + _correlation_id: String, + _request_json: String, + uid: uid_t, + ) -> Result { + let rt = Runtime::new().map_err(|e| dbus::MethodErr::failed(&format!("{:?}", e)))?; + // Only return the account for the requesting user + let user = rt + .block_on(self.cachelayer.get_usertoken(Id::Gid(uid))) + .map_err(|e| dbus::MethodErr::failed(&format!("Unable to load account: {:?}", e)))? + .ok_or(dbus::MethodErr::failed("Unable to find account"))?; + let res = json!({ + "accounts": [ + { + "givenName": user.displayname, + "homeAccountId": format!("{}.{}", user.uuid.to_string(), user.tenant_id.to_string()), + "localAccountId": user.uuid.to_string(), + "name": user.displayname, + "realm": user.tenant_id.to_string(), + "username": user.spn + } + ] + }); + Ok(res.to_string()) + } + + fn remove_account( + &mut self, + _protocol_version: String, + _correlation_id: String, + _request_json: String, + _uid: uid_t, + ) -> Result { + Err(dbus::MethodErr::failed("Not implemented")) + } + + fn acquire_prt_sso_cookie( + &mut self, + _protocol_version: String, + _correlation_id: String, + request_json: String, + uid: uid_t, + ) -> Result { + let rt = Runtime::new().map_err(|e| dbus::MethodErr::failed(&format!("{:?}", e)))?; + // Double check the user is making a request for their own account + let user = rt + .block_on(self.cachelayer.get_usertoken(Id::Gid(uid))) + .map_err(|e| dbus::MethodErr::failed(&format!("Unable to load account: {:?}", e)))? + .ok_or(dbus::MethodErr::failed("Unable to find account"))?; + let request: SsoCookieReq = serde_json::from_str(&request_json) + .map_err(|e| dbus::MethodErr::failed(&format!("{:?}", e)))?; + if request.username.to_lowercase() != user.spn.to_lowercase() { + return Err(dbus::MethodErr::failed("Invalid request for user!")); + } + rt.block_on(self.cachelayer.get_user_prt_cookie(Id::Name(user.spn))) + .ok_or(dbus::MethodErr::failed("Failed to fetch prt sso cookie")) + } + + fn generate_signed_http_request( + &mut self, + _protocol_version: String, + _correlation_id: String, + _request_json: String, + _uid: uid_t, + ) -> Result { + Err(dbus::MethodErr::failed("Not implemented")) + } + + fn cancel_interactive_flow( + &mut self, + _protocol_version: String, + _correlation_id: String, + _request_json: String, + _uid: uid_t, + ) -> Result { + Err(dbus::MethodErr::failed("Not implemented")) + } + + fn get_linux_broker_version( + &mut self, + _protocol_version: String, + _correlation_id: String, + _request_json: String, + _uid: uid_t, + ) -> Result { + let res = json!({ + "linuxBrokerVersion": env!("CARGO_PKG_VERSION"), + }); + Ok(res.to_string()) + } +} diff --git a/src/daemon/src/daemon.rs b/src/daemon/src/daemon.rs index f1563403..1667451e 100644 --- a/src/daemon/src/daemon.rs +++ b/src/daemon/src/daemon.rs @@ -53,6 +53,10 @@ use kanidm_hsm_crypto::{soft::SoftTpm, AuthValue, BoxedDynTpm, Tpm}; use notify_debouncer_full::{new_debouncer, notify::RecursiveMode, notify::Watcher}; +mod broker; +use broker::Broker; +use identity_dbus_broker::himmelblau_broker_serve; + //=== the codec type AsyncTaskRequest = (TaskRequest, oneshot::Sender<()>); @@ -209,17 +213,6 @@ async fn handle_client( trace!("Waiting for requests ..."); while let Some(Ok(req)) = reqs.next().await { let resp = match req { - ClientRequest::SshKey(account_id) => { - debug!("sshkey req"); - cachelayer - .get_sshkeys(account_id.as_str()) - .await - .map(ClientResponse::SshKeys) - .unwrap_or_else(|_| { - error!("unable to load keys, returning empty set."); - ClientResponse::SshKeys(vec![]) - }) - } ClientRequest::NssAccounts => { debug!("nssaccounts req"); cachelayer @@ -955,6 +948,16 @@ async fn main() -> ExitCode { info!("Stopped inotify watcher"); }); + // Spawn the himmelblau dbus broker + let dbus_cachelayer = cachelayer.clone(); + let task_d = tokio::spawn(async move { + if let Err(e) = himmelblau_broker_serve::(Broker { cachelayer: dbus_cachelayer }).await + { + error!("D-Bus error occurred; error = {:?}", e); + } + info!("Stopped D-Bus System Service"); + }); + // Set the umask while we open the path for most clients. let before = unsafe { umask(0) }; let listener = match UnixListener::bind(socket_path.clone()) { @@ -1052,6 +1055,7 @@ async fn main() -> ExitCode { let _ = task_a.await; let _ = task_b.await; let _ = task_c.await; + let _ = task_d.await; ExitCode::SUCCESS })