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