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