kanidm_unixd/
kanidm_unixd.rs

1#![deny(warnings)]
2#![warn(unused_extern_crates)]
3#![deny(clippy::todo)]
4#![deny(clippy::unimplemented)]
5#![deny(clippy::unwrap_used)]
6#![deny(clippy::expect_used)]
7#![deny(clippy::panic)]
8#![deny(clippy::unreachable)]
9#![deny(clippy::await_holding_lock)]
10#![deny(clippy::needless_pass_by_value)]
11#![deny(clippy::trivially_copy_pass_by_ref)]
12
13use bytes::{BufMut, BytesMut};
14use clap::{Arg, ArgAction, Command};
15use futures::{SinkExt, StreamExt};
16use kanidm_client::KanidmClientBuilder;
17use kanidm_hsm_crypto::{soft::SoftTpm, AuthValue, BoxedDynTpm, Tpm};
18use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH;
19use kanidm_proto::internal::OperationError;
20use kanidm_unix_common::constants::{
21    CODEC_BYTESMUT_ALLOCATION_LIMIT, CODEC_MIMIMUM_BYTESMUT_ALLOCATION, DEFAULT_CONFIG_PATH,
22};
23use kanidm_unix_common::unix_config::{HsmType, UnixdConfig};
24use kanidm_unix_common::unix_passwd::EtcDb;
25use kanidm_unix_common::unix_proto::{
26    ClientRequest, ClientResponse, TaskRequest, TaskRequestFrame, TaskResponse,
27};
28use kanidm_unix_resolver::db::{Cache, Db};
29use kanidm_unix_resolver::idprovider::interface::IdProvider;
30use kanidm_unix_resolver::idprovider::kanidm::KanidmProvider;
31use kanidm_unix_resolver::idprovider::system::SystemProvider;
32use kanidm_unix_resolver::resolver::Resolver;
33use kanidm_utils_users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
34use libc::umask;
35use sketching::tracing::span;
36use sketching::tracing_forest::traits::*;
37use sketching::tracing_forest::util::*;
38use sketching::tracing_forest::{self};
39use std::collections::BTreeMap;
40use std::error::Error;
41use std::fs::metadata;
42use std::io;
43use std::io::{Error as IoError, ErrorKind};
44use std::os::unix::fs::MetadataExt;
45use std::path::{Path, PathBuf};
46use std::process::ExitCode;
47use std::str::FromStr;
48use std::sync::Arc;
49use std::time::{Duration, SystemTime};
50use time::OffsetDateTime;
51use tokio::fs::File;
52use tokio::io::AsyncReadExt; // for read_to_end()
53use tokio::net::{UnixListener, UnixStream};
54use tokio::sync::broadcast;
55use tokio::sync::mpsc::{channel, Receiver, Sender};
56use tokio::sync::oneshot;
57use tokio_util::codec::{Decoder, Encoder, Framed};
58
59#[cfg(not(any(feature = "dhat-heap", target_os = "illumos")))]
60#[global_allocator]
61static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
62
63#[cfg(feature = "dhat-heap")]
64#[global_allocator]
65static ALLOC: dhat::Alloc = dhat::Alloc;
66
67//=== the codec
68
69struct AsyncTaskRequest {
70    task_req: TaskRequest,
71    task_chan: oneshot::Sender<()>,
72}
73
74#[derive(Default)]
75struct ClientCodec;
76
77impl Decoder for ClientCodec {
78    type Error = io::Error;
79    type Item = ClientRequest;
80
81    fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
82        trace!("Attempting to decode request ...");
83        match serde_json::from_slice::<ClientRequest>(src) {
84            Ok(msg) => {
85                // Clear the buffer for the next message.
86                // There is no way to free allocation from a BytesMut, so we need to
87                // swap with a new one.
88                src.clear();
89                if src.capacity() >= CODEC_BYTESMUT_ALLOCATION_LIMIT {
90                    let mut empty = BytesMut::with_capacity(CODEC_MIMIMUM_BYTESMUT_ALLOCATION);
91                    std::mem::swap(&mut empty, src);
92                }
93
94                Ok(Some(msg))
95            }
96            _ => Ok(None),
97        }
98    }
99}
100
101impl Encoder<ClientResponse> for ClientCodec {
102    type Error = io::Error;
103
104    fn encode(&mut self, msg: ClientResponse, dst: &mut BytesMut) -> Result<(), Self::Error> {
105        trace!("Attempting to send response -> {:?} ...", msg);
106        let data = serde_json::to_vec(&msg).map_err(|e| {
107            error!("socket encoding error -> {:?}", e);
108            io::Error::new(io::ErrorKind::Other, "JSON encode error")
109        })?;
110        dst.put(data.as_slice());
111        Ok(())
112    }
113}
114
115#[derive(Default)]
116struct TaskCodec;
117
118impl Decoder for TaskCodec {
119    type Error = io::Error;
120    type Item = TaskResponse;
121
122    fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
123        match serde_json::from_slice::<TaskResponse>(src) {
124            Ok(msg) => {
125                // Clear the buffer for the next message.
126                // There is no way to free allocation from a BytesMut, so we need to
127                // swap with a new one.
128                src.clear();
129                if src.capacity() >= CODEC_BYTESMUT_ALLOCATION_LIMIT {
130                    let mut empty = BytesMut::with_capacity(CODEC_MIMIMUM_BYTESMUT_ALLOCATION);
131                    std::mem::swap(&mut empty, src);
132                }
133                Ok(Some(msg))
134            }
135            _ => Ok(None),
136        }
137    }
138}
139
140impl Encoder<TaskRequestFrame> for TaskCodec {
141    type Error = io::Error;
142
143    fn encode(&mut self, msg: TaskRequestFrame, dst: &mut BytesMut) -> Result<(), Self::Error> {
144        debug!("Attempting to send request -> {:?} ...", msg.id);
145        let data = serde_json::to_vec(&msg).map_err(|e| {
146            error!("socket encoding error -> {:?}", e);
147            io::Error::new(io::ErrorKind::Other, "JSON encode error")
148        })?;
149        dst.put(data.as_slice());
150        Ok(())
151    }
152}
153
154/// Pass this a file path and it'll look for the file and remove it if it's there.
155fn rm_if_exist(p: &str) {
156    if Path::new(p).exists() {
157        debug!("Removing requested file {:?}", p);
158        let _ = std::fs::remove_file(p).map_err(|e| {
159            error!(
160                "Failure while attempting to attempting to remove {:?} -> {:?}",
161                p, e
162            );
163        });
164    } else {
165        debug!("Path {:?} doesn't exist, not attempting to remove.", p);
166    }
167}
168
169async fn handle_task_client(
170    stream: UnixStream,
171    notify_shadow_change_tx: &Sender<EtcDb>,
172    task_channel_rx: &mut Receiver<AsyncTaskRequest>,
173    broadcast_rx: &mut broadcast::Receiver<bool>,
174) -> Result<(), Box<dyn Error>> {
175    // setup the codec, this is to the unix socket which the task daemon
176    // connected to us with.
177    let mut last_task_id: u64 = 0;
178    let mut task_handles = BTreeMap::new();
179
180    let mut framed_stream = Framed::new(stream, TaskCodec);
181
182    loop {
183        tokio::select! {
184            // We have been commanded to stop operation.
185            _ = broadcast_rx.recv() => {
186                return Ok(())
187            }
188            task_request = task_channel_rx.recv() => {
189                let Some(AsyncTaskRequest {
190                    task_req,
191                    task_chan
192                }) = task_request else {
193                    // Task channel has died, cease operation.
194                    return Ok(())
195                };
196
197                debug!("Sending Task -> {:?}", task_req);
198
199                last_task_id += 1;
200                let task_id = last_task_id;
201
202                // Setup the task handle so we know who to get back to.
203                task_handles.insert(task_id, task_chan);
204
205                let task_frame = TaskRequestFrame {
206                    id: task_id,
207                    req: task_req,
208                };
209
210                if let Err(err) = framed_stream.send(task_frame).await {
211                    warn!("Unable to queue task for completion");
212                    return Err(Box::new(err));
213                }
214                // Task sent
215            }
216
217            response = framed_stream.next() => {
218                // Process incoming messages. They may be out of order.
219                match response {
220                    Some(Ok(TaskResponse::Success(task_id))) => {
221                        debug!("Task was acknowledged and completed.");
222
223                        if let Some(handle) = task_handles.remove(&task_id) {
224                            // Send a result back via the one-shot
225                            // Ignore if it fails.
226                            let _ = handle.send(());
227                        }
228                        // If the ID was unregistered, ignore.
229                    }
230                    Some(Ok(TaskResponse::NotifyShadowChange(etc_db))) => {
231                        let _ = notify_shadow_change_tx.send(etc_db).await;
232                    }
233                    // Other things ....
234                    // Some(Ok(TaskResponse::ReloadSystemIds))
235
236                    other => {
237                        error!("Error -> {:?}", other);
238                        return Err(Box::new(IoError::new(ErrorKind::Other, "oh no!")));
239                    }
240                }
241
242            }
243
244        }
245    }
246}
247
248async fn handle_client(
249    sock: UnixStream,
250    cachelayer: Arc<Resolver>,
251    task_channel_tx: &Sender<AsyncTaskRequest>,
252) -> Result<(), Box<dyn Error>> {
253    let conn_id = uuid::Uuid::new_v4();
254
255    let span = span!(Level::DEBUG, "accepted connection", uuid = %conn_id);
256    let _enter = span.enter();
257
258    let Ok(ucred) = sock.peer_cred() else {
259        return Err(Box::new(IoError::new(
260            ErrorKind::Other,
261            "Unable to verify peer credentials.",
262        )));
263    };
264
265    debug!(uid = ?ucred.uid(), gid = ?ucred.gid(), pid = ?ucred.pid());
266
267    let mut reqs = Framed::new(sock, ClientCodec);
268    let mut pam_auth_session_state = None;
269
270    // Setup a broadcast channel so that if we have an unexpected disconnection, we can
271    // tell consumers to stop work.
272    let (shutdown_tx, _shutdown_rx) = broadcast::channel(1);
273
274    debug!("Waiting for requests ...");
275    // Drop the span here so that there are no parent spans during the request loop.
276    drop(_enter);
277
278    while let Some(Ok(req)) = reqs.next().await {
279        let span = span!(Level::INFO, "client request", uuid = %conn_id, defer = true);
280        let _enter = span.enter();
281
282        let resp = match req {
283            ClientRequest::SshKey(account_id) => cachelayer
284                .get_sshkeys(account_id.as_str())
285                .await
286                .map(ClientResponse::SshKeys)
287                .unwrap_or_else(|_| {
288                    error!("unable to load keys, returning empty set.");
289                    ClientResponse::SshKeys(vec![])
290                }),
291            ClientRequest::NssAccounts => cachelayer
292                .get_nssaccounts()
293                .await
294                .map(ClientResponse::NssAccounts)
295                .unwrap_or_else(|_| {
296                    error!("unable to enum accounts");
297                    ClientResponse::NssAccounts(Vec::new())
298                }),
299            ClientRequest::NssAccountByUid(gid) => cachelayer
300                .get_nssaccount_gid(gid)
301                .await
302                .map(ClientResponse::NssAccount)
303                .unwrap_or_else(|_| {
304                    error!("unable to load account, returning empty.");
305                    ClientResponse::NssAccount(None)
306                }),
307            ClientRequest::NssAccountByName(account_id) => cachelayer
308                .get_nssaccount_name(account_id.as_str())
309                .await
310                .map(ClientResponse::NssAccount)
311                .unwrap_or_else(|_| {
312                    error!("unable to load account, returning empty.");
313                    ClientResponse::NssAccount(None)
314                }),
315            ClientRequest::NssGroups => cachelayer
316                .get_nssgroups()
317                .await
318                .map(ClientResponse::NssGroups)
319                .unwrap_or_else(|_| {
320                    error!("unable to enum groups");
321                    ClientResponse::NssGroups(Vec::new())
322                }),
323            ClientRequest::NssGroupByGid(gid) => cachelayer
324                .get_nssgroup_gid(gid)
325                .await
326                .map(ClientResponse::NssGroup)
327                .unwrap_or_else(|_| {
328                    error!("unable to load group, returning empty.");
329                    ClientResponse::NssGroup(None)
330                }),
331            ClientRequest::NssGroupByName(grp_id) => cachelayer
332                .get_nssgroup_name(grp_id.as_str())
333                .await
334                .map(ClientResponse::NssGroup)
335                .unwrap_or_else(|_| {
336                    error!("unable to load group, returning empty.");
337                    ClientResponse::NssGroup(None)
338                }),
339            ClientRequest::PamAuthenticateInit { account_id, info } => {
340                match &pam_auth_session_state {
341                    Some(_auth_session) => {
342                        // Invalid to init a request twice.
343                        warn!("Attempt to init auth session while current session is active");
344                        // Clean the former session, something is wrong.
345                        pam_auth_session_state = None;
346                        ClientResponse::Error(OperationError::KU001InitWhileSessionActive)
347                    }
348                    None => {
349                        let current_time = OffsetDateTime::now_utc();
350
351                        match cachelayer
352                            .pam_account_authenticate_init(
353                                account_id.as_str(),
354                                &info,
355                                current_time,
356                                shutdown_tx.subscribe(),
357                            )
358                            .await
359                        {
360                            Ok((auth_session, pam_auth_response)) => {
361                                pam_auth_session_state = Some(auth_session);
362                                pam_auth_response.into()
363                            }
364                            Err(_) => ClientResponse::Error(OperationError::KU004PamInitFailed),
365                        }
366                    }
367                }
368            }
369            ClientRequest::PamAuthenticateStep(pam_next_req) => match &mut pam_auth_session_state {
370                Some(auth_session) => cachelayer
371                    .pam_account_authenticate_step(auth_session, pam_next_req)
372                    .await
373                    .map(|pam_auth_response| pam_auth_response.into())
374                    .unwrap_or(ClientResponse::Error(OperationError::KU003PamAuthFailed)),
375                None => {
376                    warn!("Attempt to continue auth session while current session is inactive");
377                    ClientResponse::Error(OperationError::KU002ContinueWhileSessionInActive)
378                }
379            },
380            ClientRequest::PamAccountAllowed(account_id) => cachelayer
381                .pam_account_allowed(account_id.as_str())
382                .await
383                .map(ClientResponse::PamStatus)
384                .unwrap_or(ClientResponse::Error(
385                    OperationError::KU005ErrorCheckingAccount,
386                )),
387            ClientRequest::PamAccountBeginSession(account_id) => {
388                match cachelayer
389                    .pam_account_beginsession(account_id.as_str())
390                    .await
391                {
392                    Ok(Some(info)) => {
393                        let (tx, rx) = oneshot::channel();
394
395                        match task_channel_tx
396                            .send_timeout(
397                                AsyncTaskRequest {
398                                    task_req: TaskRequest::HomeDirectory(info),
399                                    task_chan: tx,
400                                },
401                                Duration::from_millis(100),
402                            )
403                            .await
404                        {
405                            Ok(()) => {
406                                // Now wait for the other end OR timeout.
407                                match tokio::time::timeout_at(
408                                    tokio::time::Instant::now() + Duration::from_millis(1000),
409                                    rx,
410                                )
411                                .await
412                                {
413                                    Ok(Ok(_)) => {
414                                        debug!("Task completed, returning to pam ...");
415                                        ClientResponse::Ok
416                                    }
417                                    _ => {
418                                        // Timeout or other error.
419                                        ClientResponse::Error(OperationError::KG001TaskTimeout)
420                                    }
421                                }
422                            }
423                            Err(_) => {
424                                // We could not submit the req. Move on!
425                                ClientResponse::Error(OperationError::KG002TaskCommFailure)
426                            }
427                        }
428                    }
429                    Ok(None) => {
430                        // The session can begin, but we do not need to create the home dir.
431                        ClientResponse::Ok
432                    }
433                    Err(_) => ClientResponse::Error(OperationError::KU005ErrorCheckingAccount),
434                }
435            }
436            ClientRequest::InvalidateCache => cachelayer
437                .invalidate()
438                .await
439                .map(|_| ClientResponse::Ok)
440                .unwrap_or(ClientResponse::Error(OperationError::KG003CacheClearFailed)),
441            ClientRequest::ClearCache => {
442                if ucred.uid() == 0 {
443                    cachelayer
444                        .clear_cache()
445                        .await
446                        .map(|_| ClientResponse::Ok)
447                        .unwrap_or(ClientResponse::Error(OperationError::KG003CacheClearFailed))
448                } else {
449                    error!("{}", OperationError::KU006OnlyRootAllowed);
450                    ClientResponse::Error(OperationError::KU006OnlyRootAllowed)
451                }
452            }
453            ClientRequest::Status => {
454                let status = cachelayer.provider_status().await;
455                ClientResponse::ProviderStatus(status)
456            }
457        };
458        reqs.send(resp).await?;
459        reqs.flush().await?;
460        trace!("flushed response!");
461    }
462
463    // Signal any tasks that they need to stop.
464    if let Err(shutdown_err) = shutdown_tx.send(()) {
465        warn!(
466            ?shutdown_err,
467            "Unable to signal tasks to stop, they will naturally timeout instead."
468        )
469    }
470
471    // Disconnect them
472    let span = span!(Level::DEBUG, "disconnecting client", uuid = %conn_id);
473    let _enter = span.enter();
474    debug!(uid = ?ucred.uid(), gid = ?ucred.gid(), pid = ?ucred.pid());
475
476    Ok(())
477}
478
479async fn read_hsm_pin(hsm_pin_path: &str) -> Result<Vec<u8>, Box<dyn Error>> {
480    if !PathBuf::from_str(hsm_pin_path)?.exists() {
481        return Err(std::io::Error::new(
482            std::io::ErrorKind::NotFound,
483            format!("HSM PIN file '{}' not found", hsm_pin_path),
484        )
485        .into());
486    }
487
488    let mut file = File::open(hsm_pin_path).await?;
489    let mut contents = vec![];
490    file.read_to_end(&mut contents).await?;
491    Ok(contents)
492}
493
494async fn write_hsm_pin(hsm_pin_path: &str) -> Result<(), Box<dyn Error>> {
495    if !PathBuf::from_str(hsm_pin_path)?.exists() {
496        let new_pin = AuthValue::generate().map_err(|hsm_err| {
497            error!(?hsm_err, "Unable to generate new pin");
498            std::io::Error::new(std::io::ErrorKind::Other, "Unable to generate new pin")
499        })?;
500
501        std::fs::write(hsm_pin_path, new_pin)?;
502
503        info!("Generated new HSM pin");
504    }
505
506    Ok(())
507}
508
509#[cfg(feature = "tpm")]
510fn open_tpm(tcti_name: &str) -> Option<BoxedDynTpm> {
511    use kanidm_hsm_crypto::tpm::TpmTss;
512    match TpmTss::new(tcti_name) {
513        Ok(tpm) => {
514            debug!("opened hw tpm");
515            Some(BoxedDynTpm::new(tpm))
516        }
517        Err(tpm_err) => {
518            error!(?tpm_err, "Unable to open requested tpm device");
519            None
520        }
521    }
522}
523
524#[cfg(not(feature = "tpm"))]
525fn open_tpm(_tcti_name: &str) -> Option<BoxedDynTpm> {
526    error!("Hardware TPM supported was not enabled in this build. Unable to proceed");
527    None
528}
529
530#[cfg(feature = "tpm")]
531fn open_tpm_if_possible(tcti_name: &str) -> BoxedDynTpm {
532    use kanidm_hsm_crypto::tpm::TpmTss;
533    match TpmTss::new(tcti_name) {
534        Ok(tpm) => {
535            debug!("opened hw tpm");
536            BoxedDynTpm::new(tpm)
537        }
538        Err(tpm_err) => {
539            warn!(
540                ?tpm_err,
541                "Unable to open requested tpm device, falling back to soft tpm"
542            );
543            BoxedDynTpm::new(SoftTpm::new())
544        }
545    }
546}
547
548#[cfg(not(feature = "tpm"))]
549fn open_tpm_if_possible(_tcti_name: &str) -> BoxedDynTpm {
550    debug!("opened soft tpm");
551    BoxedDynTpm::new(SoftTpm::new())
552}
553
554#[tokio::main(flavor = "current_thread")]
555async fn main() -> ExitCode {
556    // On linux when debug assertions are disabled, prevent ptrace
557    // from attaching to us.
558    #[cfg(all(target_os = "linux", not(debug_assertions)))]
559    if let Err(code) = prctl::set_dumpable(false) {
560        error!(?code, "CRITICAL: Unable to set prctl flags");
561        return ExitCode::FAILURE;
562    }
563
564    // We need enough backtrace depth to find leak sources if they exist.
565    #[cfg(feature = "dhat-heap")]
566    let _profiler = dhat::Profiler::builder()
567        .file_name(format!(
568            "/var/cache/kanidm-unixd/heap-{}.json",
569            std::process::id()
570        ))
571        .trim_backtraces(Some(40))
572        .build();
573
574    let cuid = get_current_uid();
575    let ceuid = get_effective_uid();
576    let cgid = get_current_gid();
577    let cegid = get_effective_gid();
578
579    let clap_args = Command::new("kanidm_unixd")
580        .version(env!("CARGO_PKG_VERSION"))
581        .about("Kanidm Unix daemon")
582        .arg(
583            Arg::new("skip-root-check")
584                .help("Allow running as root. Don't use this in production as it is risky!")
585                .short('r')
586                .long("skip-root-check")
587                .env("KANIDM_SKIP_ROOT_CHECK")
588                .action(ArgAction::SetTrue),
589        )
590        .arg(
591            Arg::new("debug")
592                .help("Show extra debug information")
593                .short('d')
594                .long("debug")
595                .env("KANIDM_DEBUG")
596                .action(ArgAction::SetTrue),
597        )
598        .arg(
599            Arg::new("configtest")
600                .help("Display the configuration and exit")
601                .short('t')
602                .long("configtest")
603                .action(ArgAction::SetTrue),
604        )
605        .arg(
606            Arg::new("unixd-config")
607                .help("Set the unixd config file path")
608                .short('u')
609                .long("unixd-config")
610                .default_value(DEFAULT_CONFIG_PATH)
611                .env("KANIDM_UNIX_CONFIG")
612                .action(ArgAction::Set),
613        )
614        .arg(
615            Arg::new("client-config")
616                .help("Set the client config file path")
617                .short('c')
618                .long("client-config")
619                .default_value(DEFAULT_CLIENT_CONFIG_PATH)
620                .env("KANIDM_CLIENT_CONFIG")
621                .action(ArgAction::Set),
622        )
623        .get_matches();
624
625    if clap_args.get_flag("debug") {
626        std::env::set_var("RUST_LOG", "debug");
627    }
628
629    #[allow(clippy::expect_used)]
630    tracing_forest::worker_task()
631        .set_global(true)
632        // Fall back to stderr
633        .map_sender(|sender| sender.or_stderr())
634        .build_on(|subscriber| subscriber
635            .with(EnvFilter::try_from_default_env()
636                .or_else(|_| EnvFilter::try_new("info"))
637                .expect("Failed to init envfilter")
638            )
639        )
640        .on(async {
641            let span = span!(Level::DEBUG, "starting resolver");
642            let _enter = span.enter();
643
644            if clap_args.get_flag("skip-root-check") {
645                warn!("Skipping root user check, if you're running this for testing, ensure you clean up temporary files.")
646                // TODO: this wording is not great m'kay.
647            } else if cuid == 0 || ceuid == 0 || cgid == 0 || cegid == 0 {
648                error!("Refusing to run - this process must not operate as root.");
649                return ExitCode::FAILURE
650            };
651
652            debug!("Profile -> {}", env!("KANIDM_PROFILE_NAME"));
653            debug!("CPU Flags -> {}", env!("KANIDM_CPU_FLAGS"));
654
655            let Some(cfg_path_str) = clap_args.get_one::<String>("client-config") else {
656                error!("Failed to pull the client config path");
657                return ExitCode::FAILURE
658            };
659            let cfg_path: PathBuf =  PathBuf::from(cfg_path_str);
660
661            if !cfg_path.exists() {
662                // there's no point trying to start up if we can't read a usable config!
663                error!(
664                    "Client config missing from {} - cannot start up. Quitting.",
665                    cfg_path_str
666                );
667                let diag = kanidm_lib_file_permissions::diagnose_path(cfg_path.as_ref());
668                info!(%diag);
669                return ExitCode::FAILURE
670            } else {
671                let cfg_meta = match metadata(&cfg_path) {
672                    Ok(v) => v,
673                    Err(e) => {
674                        error!("Unable to read metadata for {} - {:?}", cfg_path_str, e);
675                        let diag = kanidm_lib_file_permissions::diagnose_path(cfg_path.as_ref());
676                        info!(%diag);
677                        return ExitCode::FAILURE
678                    }
679                };
680                if !kanidm_lib_file_permissions::readonly(&cfg_meta) {
681                    warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...",
682                        cfg_path_str
683                        );
684                }
685
686                if cfg_meta.uid() == cuid || cfg_meta.uid() == ceuid {
687                    warn!("WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ...",
688                        cfg_path_str
689                    );
690                }
691            }
692
693            let Some(unixd_path_str) = clap_args.get_one::<String>("unixd-config") else {
694                error!("Failed to pull the unixd config path");
695                return ExitCode::FAILURE
696            };
697            let unixd_path = PathBuf::from(unixd_path_str);
698
699            if !unixd_path.exists() {
700                // there's no point trying to start up if we can't read a usable config!
701                error!(
702                    "unixd config missing from {} - cannot start up. Quitting.",
703                    unixd_path_str
704                );
705                let diag = kanidm_lib_file_permissions::diagnose_path(unixd_path.as_ref());
706                info!(%diag);
707                return ExitCode::FAILURE
708            } else {
709                let unixd_meta = match metadata(&unixd_path) {
710                    Ok(v) => v,
711                    Err(e) => {
712                        error!("Unable to read metadata for {} - {:?}", unixd_path_str, e);
713                        let diag = kanidm_lib_file_permissions::diagnose_path(unixd_path.as_ref());
714                        info!(%diag);
715                        return ExitCode::FAILURE
716                    }
717                };
718                if !kanidm_lib_file_permissions::readonly(&unixd_meta) {
719                    warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...",
720                        unixd_path_str);
721                }
722
723                if unixd_meta.uid() == cuid || unixd_meta.uid() == ceuid {
724                    warn!("WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ...",
725                        unixd_path_str
726                    );
727                }
728            }
729
730            let cfg = match UnixdConfig::new().read_options_from_optional_config(&unixd_path) {
731                Ok(v) => v,
732                Err(_) => {
733                    error!("Failed to parse {}", unixd_path_str);
734                    return ExitCode::FAILURE
735                }
736            };
737
738            let client_builder = if let Some(kconfig) = &cfg.kanidm_config {
739                // setup
740                let cb = match KanidmClientBuilder::new().read_options_from_optional_config(&cfg_path) {
741                    Ok(v) => v,
742                    Err(_) => {
743                        error!("Failed to parse {}", cfg_path_str);
744                        return ExitCode::FAILURE
745                    }
746                };
747
748                Some((cb, kconfig))
749            } else { None };
750
751            if clap_args.get_flag("configtest") {
752                eprintln!("###################################");
753                eprintln!("Dumping configs:\n###################################");
754                eprintln!("kanidm_unixd config (from {:#?})", &unixd_path);
755                eprintln!("{}", cfg);
756                eprintln!("###################################");
757                if let Some((cb, _)) = client_builder.as_ref() {
758                    eprintln!("kanidm client config (from {:#?})", &cfg_path);
759                    eprintln!("{}", cb);
760                }  else {
761                    eprintln!("kanidm client: disabled");
762                }
763                return ExitCode::SUCCESS;
764            }
765
766            debug!("🧹 Cleaning up sockets from previous invocations");
767            rm_if_exist(cfg.sock_path.as_str());
768            rm_if_exist(cfg.task_sock_path.as_str());
769
770            // Check the db path will be okay.
771            if !cfg.cache_db_path.is_empty() {
772                let cache_db_path = PathBuf::from(cfg.cache_db_path.as_str());
773                // We only need to check the parent folder path permissions as the db itself may not exist yet.
774                if let Some(db_parent_path) = cache_db_path.parent() {
775                    if !db_parent_path.exists() {
776                        error!(
777                            "Refusing to run, DB folder {} does not exist",
778                            db_parent_path
779                                .to_str()
780                                .unwrap_or("<db_parent_path invalid>")
781                        );
782                        let diag = kanidm_lib_file_permissions::diagnose_path(cache_db_path.as_ref());
783                        info!(%diag);
784                        return ExitCode::FAILURE
785                    }
786
787                    let db_par_path_buf = db_parent_path.to_path_buf();
788
789                    let i_meta = match metadata(&db_par_path_buf) {
790                        Ok(v) => v,
791                        Err(e) => {
792                            error!(
793                                "Unable to read metadata for {} - {:?}",
794                                db_par_path_buf
795                                    .to_str()
796                                    .unwrap_or("<db_par_path_buf invalid>"),
797                                e
798                            );
799                            return ExitCode::FAILURE
800                        }
801                    };
802
803                    if !i_meta.is_dir() {
804                        error!(
805                            "Refusing to run - DB folder {} may not be a directory",
806                            db_par_path_buf
807                                .to_str()
808                                .unwrap_or("<db_par_path_buf invalid>")
809                        );
810                        return ExitCode::FAILURE
811                    }
812                    if kanidm_lib_file_permissions::readonly(&i_meta) {
813                        warn!("WARNING: DB folder permissions on {} indicate it may not be RW. This could cause the server start up to fail!", db_par_path_buf.to_str()
814                        .unwrap_or("<db_par_path_buf invalid>")
815                        );
816                    }
817
818                    if i_meta.mode() & 0o007 != 0 {
819                        warn!("WARNING: DB folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", db_par_path_buf.to_str()
820                        .unwrap_or("<db_par_path_buf invalid>")
821                        );
822                    }
823                }
824
825                // check to see if the db's already there
826                if cache_db_path.exists() {
827                    if !cache_db_path.is_file() {
828                        error!(
829                            "Refusing to run - DB path {} already exists and is not a file.",
830                            cache_db_path.to_str().unwrap_or("<cache_db_path invalid>")
831                        );
832                        let diag = kanidm_lib_file_permissions::diagnose_path(cache_db_path.as_ref());
833                        info!(%diag);
834                        return ExitCode::FAILURE
835                    };
836
837                    match metadata(&cache_db_path) {
838                        Ok(v) => v,
839                        Err(e) => {
840                            error!(
841                                "Unable to read metadata for {} - {:?}",
842                                cache_db_path.to_str().unwrap_or("<cache_db_path invalid>"),
843                                e
844                            );
845                            let diag = kanidm_lib_file_permissions::diagnose_path(cache_db_path.as_ref());
846                            info!(%diag);
847                            return ExitCode::FAILURE
848                        }
849                    };
850                    // TODO: permissions dance to enumerate the user's ability to write to the file? ref #456 - r2d2 will happily keep trying to do things without bailing.
851                };
852            }
853
854            let db = match Db::new(cfg.cache_db_path.as_str()) {
855                Ok(db) => db,
856                Err(_e) => {
857                    error!("Failed to create database");
858                    return ExitCode::FAILURE
859                }
860            };
861
862            // perform any db migrations.
863            let mut dbtxn = db.write().await;
864            if dbtxn.migrate()
865                .and_then(|_| {
866                    dbtxn.commit()
867                }).is_err() {
868                    error!("Failed to migrate database");
869                    return ExitCode::FAILURE
870                }
871
872            // Check for and create the hsm pin if required.
873            if let Err(err) = write_hsm_pin(cfg.hsm_pin_path.as_str()).await {
874                let diag = kanidm_lib_file_permissions::diagnose_path(cfg.hsm_pin_path.as_ref());
875                info!(%diag);
876                error!(?err, "Failed to create HSM PIN into {}", cfg.hsm_pin_path.as_str());
877                return ExitCode::FAILURE
878            };
879
880            // read the hsm pin
881            let hsm_pin = match read_hsm_pin(cfg.hsm_pin_path.as_str()).await {
882                Ok(hp) => hp,
883                Err(err) => {
884                    let diag = kanidm_lib_file_permissions::diagnose_path(cfg.hsm_pin_path.as_ref());
885                    info!(%diag);
886                    error!(?err, "Failed to read HSM PIN from {}", cfg.hsm_pin_path.as_str());
887                    return ExitCode::FAILURE
888                }
889            };
890
891            let auth_value = match AuthValue::try_from(hsm_pin.as_slice()) {
892                Ok(av) => av,
893                Err(err) => {
894                    error!(?err, "invalid hsm pin");
895                    return ExitCode::FAILURE
896                }
897            };
898
899            let mut hsm: BoxedDynTpm = match cfg.hsm_type {
900                HsmType::Soft => {
901                    BoxedDynTpm::new(SoftTpm::new())
902                }
903                HsmType::TpmIfPossible => {
904                    open_tpm_if_possible(&cfg.tpm_tcti_name)
905                }
906                HsmType::Tpm => {
907                    match open_tpm(&cfg.tpm_tcti_name) {
908                        Some(hsm) => hsm,
909                        None => return ExitCode::FAILURE,
910                    }
911                }
912            };
913
914            // With the assistance of the DB, setup the HSM and its machine key.
915            let mut db_txn = db.write().await;
916
917            let loadable_machine_key = match db_txn.get_hsm_machine_key() {
918                Ok(Some(lmk)) => lmk,
919                Ok(None) => {
920                    // No machine key found - create one, and store it.
921                    let loadable_machine_key = match hsm.machine_key_create(&auth_value) {
922                        Ok(lmk) => lmk,
923                        Err(err) => {
924                            error!(?err, "Unable to create hsm loadable machine key");
925                            return ExitCode::FAILURE
926                        }
927                    };
928
929                    if let Err(err) = db_txn.insert_hsm_machine_key(&loadable_machine_key) {
930                        error!(?err, "Unable to persist hsm loadable machine key");
931                        return ExitCode::FAILURE
932                    }
933
934                    loadable_machine_key
935                }
936                Err(err) => {
937                    error!(?err, "Unable to access hsm loadable machine key");
938                    return ExitCode::FAILURE
939                }
940            };
941
942            let machine_key = match hsm.machine_key_load(&auth_value, &loadable_machine_key) {
943                Ok(mk) => mk,
944                Err(err) => {
945                    error!(?err, "Unable to load machine root key - This can occur if you have changed your HSM pin");
946                    error!("To proceed you must remove the content of the cache db ({}) to reset all keys", cfg.cache_db_path.as_str());
947                    return ExitCode::FAILURE
948                }
949            };
950
951            let Ok(system_provider) = SystemProvider::new(
952            ) else {
953                error!("Failed to configure System Provider");
954                return ExitCode::FAILURE
955            };
956
957            info!("Started system provider");
958
959            let mut clients: Vec<Arc<dyn IdProvider + Send + Sync>> = Vec::with_capacity(1);
960
961            // Setup Kanidm provider if the configuration requests it.
962            if let Some((cb, kconfig)) = client_builder {
963                let cb = cb.connect_timeout(kconfig.conn_timeout);
964                let cb = cb.request_timeout(kconfig.request_timeout);
965
966                let rsclient = match cb.build() {
967                    Ok(rsc) => rsc,
968                    Err(_e) => {
969                        error!("Failed to build async client");
970                        return ExitCode::FAILURE
971                    }
972                };
973
974                let Ok(idprovider) = KanidmProvider::new(
975                    rsclient,
976                    kconfig,
977                    SystemTime::now(),
978                    &mut (&mut db_txn).into(),
979                    &mut hsm,
980                    &machine_key
981                ) else {
982                    error!("Failed to configure Kanidm Provider");
983                    return ExitCode::FAILURE
984                };
985
986                // Now stacked for the resolver.
987                clients.push(Arc::new(idprovider));
988                info!("Started kanidm provider");
989            }
990
991            drop(machine_key);
992
993            if let Err(err) = db_txn.commit() {
994                error!(?err, "Failed to commit database transaction, unable to proceed");
995                return ExitCode::FAILURE
996            }
997
998            if !cfg.default_shell.is_empty() {
999                let shell_path = PathBuf::from_str(&cfg.default_shell).expect("Failed to build a representation of your default_shell path!");
1000                if !shell_path.exists() {
1001                    error!("Cannot find configured default shell at {}, this could cause login issues!", shell_path.display())
1002                }
1003            }
1004
1005            // Okay, the hsm is now loaded and ready to go.
1006            let cl_inner = match Resolver::new(
1007                db,
1008                Arc::new(system_provider),
1009                clients,
1010                hsm,
1011                cfg.cache_timeout,
1012                cfg.default_shell.clone(),
1013                cfg.home_prefix.clone(),
1014                cfg.home_attr,
1015                cfg.home_alias,
1016                cfg.uid_attr_map,
1017                cfg.gid_attr_map,
1018            )
1019            .await
1020            {
1021                Ok(c) => c,
1022                Err(_e) => {
1023                    error!("Failed to build cache layer.");
1024                    return ExitCode::FAILURE
1025                }
1026            };
1027
1028            let cachelayer = Arc::new(cl_inner);
1029
1030            // Setup the root-only tasks socket. Take away all other access bits.
1031            let before = unsafe { umask(0o0077) };
1032            let task_listener = match UnixListener::bind(cfg.task_sock_path.as_str()) {
1033                Ok(l) => l,
1034                Err(_e) => {
1035                    let diag = kanidm_lib_file_permissions::diagnose_path(cfg.task_sock_path.as_ref());
1036                    info!(%diag);
1037                    error!("Failed to bind UNIX socket {}", cfg.task_sock_path.as_str());
1038                    return ExitCode::FAILURE
1039                }
1040            };
1041            // Undo umask changes.
1042            let _ = unsafe { umask(before) };
1043
1044            // The tasks ... well task. Tasks-task. Anyway, the tasks-task is bidirectional
1045            // in its communication to the tasks-daemon. We submit tasks to the tasks-daemon
1046            // via this channel here -\
1047            //                        |
1048            //                        v
1049            let (task_channel_tx, mut task_channel_rx) = channel(16);
1050            let task_channel_tx = Arc::new(task_channel_tx);
1051            let task_channel_tx_cln = task_channel_tx.clone();
1052            // Each submitted task contains a oneshot channel allowing the tasks-task to
1053            // notify the submitter of the task that the task is completed.
1054
1055            // This channel is for the second case - the tasks-daemon can send us
1056            // unsolicited dm's about system state, and when these occure we need to
1057            // response to these notifications. Because each potential dm that the
1058            // daemon can send us has a specific intent, we need a channel for each
1059            // type of notification that we could get. This channel is for when
1060            // the tasks daemon has a reloaded shadow database for us to process
1061            // and cache:
1062            let (notify_shadow_channel_tx, mut notify_shadow_channel_rx) = channel(16);
1063            let notify_shadow_channel_tx = Arc::new(notify_shadow_channel_tx);
1064
1065            // Broadcast receivers so that the tasks-task can be shut down when we get
1066            // signals etc.
1067            let (broadcast_tx, mut broadcast_rx) = broadcast::channel(4);
1068            let mut c_broadcast_rx = broadcast_tx.subscribe();
1069            let mut d_broadcast_rx = broadcast_tx.subscribe();
1070
1071            let task_b = tokio::spawn(async move {
1072                loop {
1073                    tokio::select! {
1074                        // Wait on the broadcast to see if we need to close down.
1075                        _ = c_broadcast_rx.recv() => {
1076                            break;
1077                        }
1078                        accept_res = task_listener.accept() => {
1079                            match accept_res {
1080                                Ok((socket, _addr)) => {
1081                                    // Did it come from root? If not, they don't have the needed
1082                                    // permissions to actually be a task handler, and we wouldn't
1083                                    // want to leak to anyone else anyway.
1084                                    if let Ok(ucred) = socket.peer_cred() {
1085                                        if ucred.uid() != 0 {
1086                                            // move along.
1087                                            warn!("Task handler not running as root, ignoring ...");
1088                                            continue;
1089                                        }
1090                                    } else {
1091                                        // move along.
1092                                        warn!("Unable to determine socked peer cred, ignoring ...");
1093                                        continue;
1094                                    };
1095                                    debug!("A task handler has connected.");
1096                                    // It did? Great, now we can wait and spin on that one
1097                                    // client.
1098
1099                                    // We have to check for signals here else this tasks waits forever.
1100                                    if let Err(err) = handle_task_client(socket, &notify_shadow_channel_tx, &mut task_channel_rx, &mut d_broadcast_rx).await {
1101                                        error!(?err, "Task client error occurred");
1102                                    }
1103                                    // If they disconnect we go back to accept.
1104                                }
1105                                Err(err) => {
1106                                    error!("Task Accept error -> {:?}", err);
1107                                }
1108                            }
1109                        }
1110                    }
1111                    // done
1112                }
1113                info!("Stopped task connector");
1114            });
1115
1116            // ====== Listen for shadow change notification from tasks ======
1117
1118            let shadow_notify_cachelayer = cachelayer.clone();
1119            let mut c_broadcast_rx = broadcast_tx.subscribe();
1120
1121            let task_c = tokio::spawn(async move {
1122                debug!("Spawned shadow reload task handler");
1123                loop {
1124                    tokio::select! {
1125                        _ = c_broadcast_rx.recv() => {
1126                            break;
1127                        }
1128                        Some(EtcDb {
1129                            users, shadow, groups
1130                        }) = notify_shadow_channel_rx.recv() => {
1131                            shadow_notify_cachelayer
1132                                .reload_system_identities(users, shadow, groups)
1133                                .await;
1134                        }
1135                    }
1136                }
1137                info!("Stopped shadow reload task handler");
1138            });
1139
1140            // TODO: Setup a task that handles pre-fetching here.
1141
1142            // Set the umask while we open the path for most clients.
1143            let before = unsafe { umask(0) };
1144            let listener = match UnixListener::bind(cfg.sock_path.as_str()) {
1145                Ok(l) => l,
1146                Err(_e) => {
1147                    error!("Failed to bind UNIX socket at {}", cfg.sock_path.as_str());
1148                    return ExitCode::FAILURE
1149                }
1150            };
1151            // Undo umask changes.
1152            let _ = unsafe { umask(before) };
1153
1154            let task_a = tokio::spawn(async move {
1155                loop {
1156                    let tc_tx = task_channel_tx_cln.clone();
1157
1158                    tokio::select! {
1159                        _ = broadcast_rx.recv() => {
1160                            break;
1161                        }
1162                        accept_res = listener.accept() => {
1163                            match accept_res {
1164                                Ok((socket, _addr)) => {
1165                                    let cachelayer_ref = cachelayer.clone();
1166                                    tokio::spawn(async move {
1167                                        if let Err(e) = handle_client(socket, cachelayer_ref.clone(), &tc_tx).await
1168                                        {
1169                                            error!("handle_client error occurred; error = {:?}", e);
1170                                        }
1171                                    });
1172                                }
1173                                Err(err) => {
1174                                    error!("Error while handling connection -> {:?}", err);
1175                                }
1176                            }
1177                        }
1178                    }
1179
1180                }
1181                info!("Stopped resolver");
1182            });
1183
1184            info!("Server started ...");
1185
1186            // End the startup span, we can now proceed.
1187            drop(_enter);
1188
1189            // On linux, notify systemd.
1190            #[cfg(target_os = "linux")]
1191            let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
1192
1193            loop {
1194                tokio::select! {
1195                    Ok(()) = tokio::signal::ctrl_c() => {
1196                        break
1197                    }
1198                    Some(()) = async move {
1199                        let sigterm = tokio::signal::unix::SignalKind::terminate();
1200                        #[allow(clippy::unwrap_used)]
1201                        tokio::signal::unix::signal(sigterm).unwrap().recv().await
1202                    } => {
1203                        break
1204                    }
1205                    Some(()) = async move {
1206                        let sigterm = tokio::signal::unix::SignalKind::alarm();
1207                        #[allow(clippy::unwrap_used)]
1208                        tokio::signal::unix::signal(sigterm).unwrap().recv().await
1209                    } => {
1210                        // Ignore
1211                    }
1212                    Some(()) = async move {
1213                        let sigterm = tokio::signal::unix::SignalKind::hangup();
1214                        #[allow(clippy::unwrap_used)]
1215                        tokio::signal::unix::signal(sigterm).unwrap().recv().await
1216                    } => {
1217                        // Ignore
1218                    }
1219                    Some(()) = async move {
1220                        let sigterm = tokio::signal::unix::SignalKind::user_defined1();
1221                        #[allow(clippy::unwrap_used)]
1222                        tokio::signal::unix::signal(sigterm).unwrap().recv().await
1223                    } => {
1224                        // Ignore
1225                    }
1226                    Some(()) = async move {
1227                        let sigterm = tokio::signal::unix::SignalKind::user_defined2();
1228                        #[allow(clippy::unwrap_used)]
1229                        tokio::signal::unix::signal(sigterm).unwrap().recv().await
1230                    } => {
1231                        // Ignore
1232                    }
1233                }
1234            }
1235            info!("Signal received, sending down signal to tasks");
1236            // Send a broadcast that we are done.
1237            if let Err(e) = broadcast_tx.send(true) {
1238                error!("Unable to shutdown workers {:?}", e);
1239            }
1240
1241            let _ = task_a.await;
1242            let _ = task_b.await;
1243            let _ = task_c.await;
1244
1245            ExitCode::SUCCESS
1246    })
1247    .await
1248    // TODO: can we catch signals to clean up sockets etc, especially handy when running as root
1249}