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