kanidm_unixd/
kanidm_unixd.rs

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