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