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