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