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