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