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