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
13#[cfg(all(not(feature = "dhat-heap"), target_os = "linux"))]
14#[global_allocator]
15static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
16
17#[cfg(feature = "dhat-heap")]
18#[global_allocator]
19static ALLOC: dhat::Alloc = dhat::Alloc;
20
21use std::fs::{metadata, File};
22use fs4::fs_std::FileExt;
24use kanidm_proto::messages::ConsoleOutputMode;
25use sketching::otel::TracingPipelineGuard;
26use std::io::Read;
27#[cfg(target_family = "unix")]
28use std::os::unix::fs::MetadataExt;
29use std::path::PathBuf;
30use std::process::ExitCode;
31
32use clap::{Args, Parser, Subcommand};
33use futures::{SinkExt, StreamExt};
34#[cfg(not(target_family = "windows"))] use kanidm_utils_users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
36use kanidmd_core::admin::{
37 AdminTaskRequest, AdminTaskResponse, ClientCodec, ProtoDomainInfo,
38 ProtoDomainUpgradeCheckReport, ProtoDomainUpgradeCheckStatus,
39};
40use kanidmd_core::config::{CliConfig, Configuration, EnvironmentConfig, ServerConfigUntagged};
41use kanidmd_core::{
42 backup_server_core, cert_generate_core, create_server_core, dbscan_get_id2entry_core,
43 dbscan_list_id2entry_core, dbscan_list_index_analysis_core, dbscan_list_index_core,
44 dbscan_list_indexes_core, dbscan_list_quarantined_core, dbscan_quarantine_id2entry_core,
45 dbscan_restore_quarantined_core, domain_rename_core, reindex_server_core, restore_server_core,
46 vacuum_server_core, verify_server_core,
47};
48use sketching::tracing_forest::util::*;
49use tokio::net::UnixStream;
50use tokio_util::codec::Framed;
51#[cfg(target_family = "windows")] use whoami;
53
54include!("./opt.rs");
55
56#[cfg(target_family = "windows")]
58fn get_user_details_windows() {
59 eprintln!(
60 "Running on windows, current username is: {:?}",
61 whoami::username()
62 );
63}
64
65async fn submit_admin_req(path: &str, req: AdminTaskRequest, output_mode: ConsoleOutputMode) {
66 let stream = match UnixStream::connect(path).await {
68 Ok(s) => s,
69 Err(e) => {
70 error!(err = ?e, %path, "Unable to connect to socket path");
71 let diag = kanidm_lib_file_permissions::diagnose_path(path.as_ref());
72 info!(%diag);
73 return;
74 }
75 };
76
77 let mut reqs = Framed::new(stream, ClientCodec);
78
79 if let Err(e) = reqs.send(req).await {
80 error!(err = ?e, "Unable to send request");
81 return;
82 };
83
84 if let Err(e) = reqs.flush().await {
85 error!(err = ?e, "Unable to flush request");
86 return;
87 }
88
89 trace!("flushed, waiting ...");
90
91 match reqs.next().await {
92 Some(Ok(AdminTaskResponse::RecoverAccount { password })) => match output_mode {
93 ConsoleOutputMode::JSON => {
94 let json_output = serde_json::json!({
95 "password": password
96 });
97 println!("{json_output}");
98 }
99 ConsoleOutputMode::Text => {
100 info!(new_password = ?password)
101 }
102 },
103 Some(Ok(AdminTaskResponse::ShowReplicationCertificate { cert })) => match output_mode {
104 ConsoleOutputMode::JSON => {
105 println!("{{\"certificate\":\"{cert}\"}}")
106 }
107 ConsoleOutputMode::Text => {
108 info!(certificate = ?cert)
109 }
110 },
111
112 Some(Ok(AdminTaskResponse::DomainUpgradeCheck { report })) => {
113 match output_mode {
114 ConsoleOutputMode::JSON => {
115 let json_output = serde_json::json!({
116 "domain_upgrade_check": report
117 });
118 println!("{json_output}");
119 }
120 ConsoleOutputMode::Text => {
121 let ProtoDomainUpgradeCheckReport {
122 name,
123 uuid,
124 current_level,
125 upgrade_level,
126 report_items,
127 } = report;
128
129 info!("domain_name : {}", name);
130 info!("domain_uuid : {}", uuid);
131 info!("domain_current_level : {}", current_level);
132 info!("domain_upgrade_level : {}", upgrade_level);
133
134 for item in report_items {
135 info!("------------------------");
136 match item.status {
137 ProtoDomainUpgradeCheckStatus::Pass6To7Gidnumber => {
138 info!("upgrade_item : gidnumber range validity");
139 debug!("from_level : {}", item.from_level);
140 debug!("to_level : {}", item.to_level);
141 info!("status : PASS");
142 }
143 ProtoDomainUpgradeCheckStatus::Fail6To7Gidnumber => {
144 info!("upgrade_item : gidnumber range validity");
145 debug!("from_level : {}", item.from_level);
146 debug!("to_level : {}", item.to_level);
147 info!("status : FAIL");
148 info!("description : The automatically allocated gidnumbers for posix accounts was found to allocate numbers into systemd-reserved ranges. These can no longer be used.");
149 info!("action : Modify the gidnumber of affected entries so that they are in the range 65536 to 524287 OR reset the gidnumber to cause it to automatically regenerate.");
150 for entry_id in item.affected_entries {
151 info!("affected_entry : {}", entry_id);
152 }
153 }
154 ProtoDomainUpgradeCheckStatus::Pass7To8SecurityKeys => {
156 info!("upgrade_item : security key usage");
157 debug!("from_level : {}", item.from_level);
158 debug!("to_level : {}", item.to_level);
159 info!("status : PASS");
160 }
161 ProtoDomainUpgradeCheckStatus::Fail7To8SecurityKeys => {
162 info!("upgrade_item : security key usage");
163 debug!("from_level : {}", item.from_level);
164 debug!("to_level : {}", item.to_level);
165 info!("status : FAIL");
166 info!("description : Security keys no longer function as a second factor due to the introduction of CTAP2 and greater forcing PIN interactions.");
167 info!("action : Modify the accounts in question to remove their security key and add it as a passkey or enable TOTP");
168 for entry_id in item.affected_entries {
169 info!("affected_entry : {}", entry_id);
170 }
171 }
172 ProtoDomainUpgradeCheckStatus::Pass7To8Oauth2StrictRedirectUri => {
174 info!("upgrade_item : oauth2 strict redirect uri enforcement");
175 debug!("from_level : {}", item.from_level);
176 debug!("to_level : {}", item.to_level);
177 info!("status : PASS");
178 }
179 ProtoDomainUpgradeCheckStatus::Fail7To8Oauth2StrictRedirectUri => {
180 info!("upgrade_item : oauth2 strict redirect uri enforcement");
181 debug!("from_level : {}", item.from_level);
182 debug!("to_level : {}", item.to_level);
183 info!("status : FAIL");
184 info!("description : To harden against possible public client open redirection vulnerabilities, redirect uris must now be registered ahead of time and are validated rather than the former origin verification process.");
185 info!("action : Verify the redirect uri's for OAuth2 clients and then enable strict-redirect-uri on each client.");
186 for entry_id in item.affected_entries {
187 info!("affected_entry : {}", entry_id);
188 }
189 }
190 }
191 }
192 }
193 }
194 }
195
196 Some(Ok(AdminTaskResponse::DomainRaise { level })) => match output_mode {
197 ConsoleOutputMode::JSON => {
198 eprintln!("{{\"success\":\"{level}\"}}")
199 }
200 ConsoleOutputMode::Text => {
201 info!("success - raised domain level to {}", level)
202 }
203 },
204 Some(Ok(AdminTaskResponse::DomainShow { domain_info })) => match output_mode {
205 ConsoleOutputMode::JSON => {
206 let json_output = serde_json::json!({
207 "domain_info": domain_info
208 });
209 println!("{json_output}");
210 }
211 ConsoleOutputMode::Text => {
212 let ProtoDomainInfo {
213 name,
214 displayname,
215 uuid,
216 level,
217 } = domain_info;
218
219 info!("domain_name : {}", name);
220 info!("domain_display: {}", displayname);
221 info!("domain_uuid : {}", uuid);
222 info!("domain_level : {}", level);
223 }
224 },
225 Some(Ok(AdminTaskResponse::Success)) => match output_mode {
226 ConsoleOutputMode::JSON => {
227 eprintln!("\"success\"")
228 }
229 ConsoleOutputMode::Text => {
230 info!("success")
231 }
232 },
233 Some(Ok(AdminTaskResponse::Error)) => match output_mode {
234 ConsoleOutputMode::JSON => {
235 eprintln!("\"error\"")
236 }
237 ConsoleOutputMode::Text => {
238 info!("Error - you should inspect the logs.")
239 }
240 },
241 Some(Err(err)) => {
242 error!(?err, "Error during admin task operation");
243 }
244 None => {
245 error!("Error making request to admin socket");
246 }
247 }
248}
249
250fn check_file_ownership(opt: &KanidmdParser) -> Result<(), ExitCode> {
252 #[cfg(target_family = "unix")]
254 let (cuid, ceuid) = {
255 let cuid = get_current_uid();
256 let ceuid = get_effective_uid();
257 let cgid = get_current_gid();
258 let cegid = get_effective_gid();
259
260 if cuid == 0 || ceuid == 0 || cgid == 0 || cegid == 0 {
261 warn!("This is running as uid == 0 (root) which may be a security risk.");
262 }
265
266 if cuid != ceuid || cgid != cegid {
267 error!("{} != {} || {} != {}", cuid, ceuid, cgid, cegid);
268 error!("Refusing to run - uid and euid OR gid and egid must be consistent.");
269 return Err(ExitCode::FAILURE);
270 }
271 (cuid, ceuid)
272 };
273
274 if let Some(cfg_path) = &opt.config_path {
275 #[cfg(target_family = "unix")]
276 {
277 if let Some(cfg_meta) = match metadata(cfg_path) {
278 Ok(m) => Some(m),
279 Err(e) => {
280 error!(
281 "Unable to read metadata for configuration file '{}' - {:?}",
282 cfg_path.display(),
283 e
284 );
285 None
287 }
288 } {
289 if !kanidm_lib_file_permissions::readonly(&cfg_meta) {
290 warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...",
291 cfg_path.to_str().unwrap_or("invalid file path"));
292 }
293
294 if cfg_meta.mode() & 0o007 != 0 {
295 warn!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...",
296 cfg_path.to_str().unwrap_or("invalid file path")
297 );
298 }
299
300 if cfg_meta.uid() == cuid || cfg_meta.uid() == ceuid {
301 warn!("WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ...",
302 cfg_path.to_str().unwrap_or("invalid file path")
303 );
304 }
305 }
306 }
307 }
308 Ok(())
309}
310
311async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
313 let sub = match sketching::otel::start_logging_pipeline(
317 &config.otel_grpc_url,
318 config.log_level,
319 "kanidmd",
320 ) {
321 Err(err) => {
322 eprintln!("Error starting logger - {err:} - Bailing on startup!");
323 return ExitCode::FAILURE;
324 }
325 Ok(val) => val,
326 };
327
328 if let Err(err) = tracing::subscriber::set_global_default(sub).map_err(|err| {
329 eprintln!("Error starting logger - {err:} - Bailing on startup!");
330 ExitCode::FAILURE
331 }) {
332 return err;
333 };
334
335 info!(version = %env!("KANIDM_PKG_VERSION"), "Starting Kanidmd");
340
341 let _otelguard = TracingPipelineGuard {};
343
344 if let Err(err) = check_file_ownership(&opt) {
349 return err;
350 };
351
352 if let Some(db_path) = config.db_path.as_ref() {
353 let db_pathbuf = db_path.to_path_buf();
354 if let Some(db_parent_path) = db_pathbuf.parent() {
356 if !db_parent_path.exists() {
357 warn!(
358 "DB folder {} may not exist, server startup may FAIL!",
359 db_parent_path.to_str().unwrap_or("invalid file path")
360 );
361 let diag = kanidm_lib_file_permissions::diagnose_path(&db_pathbuf);
362 info!(%diag);
363 }
364
365 let db_par_path_buf = db_parent_path.to_path_buf();
366 let i_meta = match metadata(&db_par_path_buf) {
367 Ok(m) => m,
368 Err(e) => {
369 error!(
370 "Unable to read metadata for database folder '{}' - {:?}",
371 &db_par_path_buf.to_str().unwrap_or("invalid file path"),
372 e
373 );
374 return ExitCode::FAILURE;
375 }
376 };
377 if !i_meta.is_dir() {
378 error!(
379 "ERROR: Refusing to run - DB folder {} may not be a directory",
380 db_par_path_buf.to_str().unwrap_or("invalid file path")
381 );
382 return ExitCode::FAILURE;
383 }
384
385 if kanidm_lib_file_permissions::readonly(&i_meta) {
386 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().unwrap_or("invalid file path"));
387 }
388 #[cfg(not(target_os = "windows"))]
389 if i_meta.mode() & 0o007 != 0 {
390 warn!("WARNING: DB folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", db_par_path_buf.to_str().unwrap_or("invalid file path"));
391 }
392 }
393 } else {
394 error!("No db_path set in configuration, server startup will FAIL!");
395 return ExitCode::FAILURE;
396 }
397
398 let lock_was_setup = match &opt.commands {
399 KanidmdOpt::ShowReplicationCertificate
401 | KanidmdOpt::RenewReplicationCertificate
402 | KanidmdOpt::RefreshReplicationConsumer { .. }
403 | KanidmdOpt::RecoverAccount { .. }
404 | KanidmdOpt::DisableAccount { .. }
405 | KanidmdOpt::HealthCheck(_) => None,
406 _ => {
407 #[allow(clippy::expect_used)]
409 let klock_path = match config.db_path.clone() {
410 Some(val) => val.with_extension("klock"),
411 None => std::env::temp_dir().join("kanidmd.klock"),
412 };
413
414 let flock = match File::create(&klock_path) {
415 Ok(flock) => flock,
416 Err(err) => {
417 error!(
418 "ERROR: Refusing to start - unable to create kanidmd exclusive lock at {}",
419 klock_path.display()
420 );
421 error!(?err);
422 return ExitCode::FAILURE;
423 }
424 };
425
426 match flock.try_lock_exclusive() {
427 Ok(true) => debug!("Acquired kanidm exclusive lock"),
428 Ok(false) => {
429 error!(
430 "ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {}",
431 klock_path.display()
432 );
433 error!("Is another kanidmd process running?");
434 return ExitCode::FAILURE;
435 }
436 Err(err) => {
437 error!(
438 "ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {}",
439 klock_path.display()
440 );
441 error!(?err);
442 return ExitCode::FAILURE;
443 }
444 };
445
446 Some(klock_path)
447 }
448 };
449
450 let result_code = kanidm_main(config, opt).await;
451
452 if let Some(klock_path) = lock_was_setup {
453 if let Err(reason) = std::fs::remove_file(&klock_path) {
454 warn!(
455 ?reason,
456 "WARNING: Unable to clean up kanidmd exclusive lock at {}",
457 klock_path.display()
458 );
459 }
460 }
461
462 result_code
463}
464
465fn main() -> ExitCode {
466 #[cfg(all(target_os = "linux", not(debug_assertions)))]
469 if let Err(code) = prctl::set_dumpable(false) {
470 println!(
471 "CRITICAL: Unable to set prctl flags, which breaches our security model, quitting! {:?}", code
472 );
473 return ExitCode::FAILURE;
474 }
475
476 #[cfg(feature = "dhat-heap")]
478 let _profiler = dhat::Profiler::builder().trim_backtraces(Some(40)).build();
479
480 let opt = KanidmdParser::parse();
482
483 if let KanidmdOpt::Version = &opt.commands {
485 println!("kanidmd {}", env!("KANIDM_PKG_VERSION"));
486 return ExitCode::SUCCESS;
487 };
488
489 if env!("KANIDM_SERVER_CONFIG_PATH").is_empty() {
490 println!("CRITICAL: Kanidmd was not built correctly and is missing a valid KANIDM_SERVER_CONFIG_PATH value");
491 return ExitCode::FAILURE;
492 }
493
494 let default_config_path = PathBuf::from(env!("KANIDM_SERVER_CONFIG_PATH"));
495
496 let maybe_config_path = if let Some(p) = &opt.config_path {
497 Some(p.clone())
498 } else {
499 if default_config_path.exists() {
501 Some(default_config_path)
503 } else {
504 None
507 }
508 };
509
510 let maybe_sconfig = if let Some(config_path) = maybe_config_path {
511 match ServerConfigUntagged::new(config_path) {
512 Ok(c) => Some(c),
513 Err(err) => {
514 eprintln!("ERROR: Configuration Parse Failure: {err:?}");
515 return ExitCode::FAILURE;
516 }
517 }
518 } else {
519 eprintln!("WARNING: No configuration path was provided, relying on environment variables.");
520 None
521 };
522
523 let envconfig = match EnvironmentConfig::new() {
524 Ok(ec) => ec,
525 Err(err) => {
526 eprintln!("ERROR: Environment Configuration Parse Failure: {err:?}");
527 return ExitCode::FAILURE;
528 }
529 };
530
531 let cli_config = CliConfig {
532 output_mode: Some(opt.output_mode.to_owned().into()),
533 };
534
535 let is_server = matches!(&opt.commands, KanidmdOpt::Server);
536
537 let config = Configuration::build()
538 .add_env_config(envconfig)
539 .add_opt_toml_config(maybe_sconfig)
540 .add_cli_config(cli_config)
542 .is_server_mode(is_server)
543 .finish();
544
545 let Some(config) = config else {
546 eprintln!(
547 "ERROR: Unable to build server configuration from provided configuration inputs."
548 );
549 return ExitCode::FAILURE;
550 };
551
552 #[cfg(target_family = "windows")]
557 get_user_details_windows();
558
559 let maybe_rt = tokio::runtime::Builder::new_multi_thread()
561 .worker_threads(config.threads)
562 .enable_all()
563 .thread_name("kanidmd-thread-pool")
564 .build();
570
571 let rt = match maybe_rt {
572 Ok(rt) => rt,
573 Err(err) => {
574 eprintln!("CRITICAL: Unable to start runtime! {err:?}");
575 return ExitCode::FAILURE;
576 }
577 };
578
579 rt.block_on(start_daemon(opt, config))
580}
581
582async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
585 match &opt.commands {
586 KanidmdOpt::Server | KanidmdOpt::ConfigTest => {
587 let config_test = matches!(&opt.commands, KanidmdOpt::ConfigTest);
588 if config_test {
589 info!("Running in server configuration test mode ...");
590 } else {
591 info!("Running in server mode ...");
592 };
593
594 if let Some(tls_config) = config.tls_config.as_ref() {
596 {
597 let i_meta = match metadata(&tls_config.chain) {
598 Ok(m) => m,
599 Err(e) => {
600 error!(
601 "Unable to read metadata for TLS chain file '{}' - {:?}",
602 tls_config.chain.display(),
603 e
604 );
605 let diag =
606 kanidm_lib_file_permissions::diagnose_path(&tls_config.chain);
607 info!(%diag);
608 return ExitCode::FAILURE;
609 }
610 };
611 if !kanidm_lib_file_permissions::readonly(&i_meta) {
612 warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", tls_config.chain.display());
613 }
614 }
615
616 {
617 let i_meta = match metadata(&tls_config.key) {
618 Ok(m) => m,
619 Err(e) => {
620 error!(
621 "Unable to read metadata for TLS key file '{}' - {:?}",
622 tls_config.key.display(),
623 e
624 );
625 let diag = kanidm_lib_file_permissions::diagnose_path(&tls_config.key);
626 info!(%diag);
627 return ExitCode::FAILURE;
628 }
629 };
630 if !kanidm_lib_file_permissions::readonly(&i_meta) {
631 warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", tls_config.key.display());
632 }
633 #[cfg(not(target_os = "windows"))]
634 if i_meta.mode() & 0o007 != 0 {
635 warn!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", tls_config.key.display());
636 }
637 }
638
639 if let Some(ca_dir) = tls_config.client_ca.as_ref() {
640 let ca_dir_path = PathBuf::from(&ca_dir);
642 if !ca_dir_path.exists() {
643 error!(
644 "TLS CA folder {} does not exist, server startup will FAIL!",
645 ca_dir.display()
646 );
647 let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
648 info!(%diag);
649 }
650
651 let i_meta = match metadata(&ca_dir_path) {
652 Ok(m) => m,
653 Err(e) => {
654 error!(
655 "Unable to read metadata for '{}' - {:?}",
656 ca_dir.display(),
657 e
658 );
659 let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
660 info!(%diag);
661 return ExitCode::FAILURE;
662 }
663 };
664 if !i_meta.is_dir() {
665 error!(
666 "ERROR: Refusing to run - TLS Client CA folder {} may not be a directory",
667 ca_dir.display()
668 );
669 return ExitCode::FAILURE;
670 }
671 if kanidm_lib_file_permissions::readonly(&i_meta) {
672 warn!("WARNING: TLS Client CA folder permissions on {} indicate it may not be RW. This could cause the server start up to fail!", ca_dir.display());
673 }
674 #[cfg(not(target_os = "windows"))]
675 if i_meta.mode() & 0o007 != 0 {
676 warn!("WARNING: TLS Client CA folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", ca_dir.display());
677 }
678 }
679 }
680
681 let sctx = create_server_core(config, config_test).await;
682 if !config_test {
683 #[cfg(target_os = "linux")]
685 {
686 let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
687 let _ = sd_notify::notify(
688 true,
689 &[sd_notify::NotifyState::Status("Started Kanidm 🦀")],
690 );
691 };
692
693 match sctx {
694 Ok(mut sctx) => {
695 loop {
696 #[cfg(target_family = "unix")]
697 {
698 let mut listener = sctx.subscribe();
699 tokio::select! {
700 Ok(()) = tokio::signal::ctrl_c() => {
701 break
702 }
703 Some(()) = async move {
704 let sigterm = tokio::signal::unix::SignalKind::terminate();
705 #[allow(clippy::unwrap_used)]
706 tokio::signal::unix::signal(sigterm).unwrap().recv().await
707 } => {
708 break
709 }
710 Some(()) = async move {
711 let sigterm = tokio::signal::unix::SignalKind::alarm();
712 #[allow(clippy::unwrap_used)]
713 tokio::signal::unix::signal(sigterm).unwrap().recv().await
714 } => {
715 }
717 Some(()) = async move {
718 let sigterm = tokio::signal::unix::SignalKind::hangup();
719 #[allow(clippy::unwrap_used)]
720 tokio::signal::unix::signal(sigterm).unwrap().recv().await
721 } => {
722 #[cfg(target_os = "linux")]
725 {
726 if let Ok(monotonic_usec) = sd_notify::NotifyState::monotonic_usec_now() {
727 let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Reloading, monotonic_usec]);
728 let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Status("Reloading ...")]);
729 } else {
730 error!("CRITICAL!!! Unable to access clock monotonic time. SYSTEMD WILL KILL US.");
731 };
732 }
733
734 sctx.tls_acceptor_reload().await;
735
736 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
739
740 #[cfg(target_os = "linux")]
741 {
742 if let Ok(monotonic_usec) = sd_notify::NotifyState::monotonic_usec_now() {
743 let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready, monotonic_usec]);
744 let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Status("Reload Success")]);
745 } else {
746 error!("CRITICAL!!! Unable to access clock monotonic time. SYSTEMD WILL KILL US.");
747 };
748 }
749
750 info!("Reload complete");
751 }
752 Some(()) = async move {
753 let sigterm = tokio::signal::unix::SignalKind::user_defined1();
754 #[allow(clippy::unwrap_used)]
755 tokio::signal::unix::signal(sigterm).unwrap().recv().await
756 } => {
757 }
759 Some(()) = async move {
760 let sigterm = tokio::signal::unix::SignalKind::user_defined2();
761 #[allow(clippy::unwrap_used)]
762 tokio::signal::unix::signal(sigterm).unwrap().recv().await
763 } => {
764 }
766 Ok(msg) = async move {
768 listener.recv().await
769 } => {
770 debug!("Main loop received message: {:?}", msg);
771 break
772 }
773 }
774 }
775 #[cfg(target_family = "windows")]
776 {
777 tokio::select! {
778 Ok(()) = tokio::signal::ctrl_c() => {
779 break
780 }
781 }
782 }
783 }
784 info!("Signal received, shutting down");
785 sctx.shutdown().await;
787 }
788 Err(_) => {
789 error!("Failed to start server core!");
790 return ExitCode::FAILURE;
793 }
794 }
795 info!("Stopped 🛑 ");
796 }
797 }
798 KanidmdOpt::CertGenerate => {
799 info!("Running in certificate generate mode ...");
800 cert_generate_core(&config);
801 }
802 KanidmdOpt::Database {
803 commands: DbCommands::Backup(bopt),
804 } => {
805 info!("Running in backup mode ...");
806 backup_server_core(&config, &bopt.path);
807 }
808 KanidmdOpt::Database {
809 commands: DbCommands::Restore(ropt),
810 } => {
811 info!("Running in restore mode ...");
812 restore_server_core(&config, &ropt.path).await;
813 }
814 KanidmdOpt::Database {
815 commands: DbCommands::Verify,
816 } => {
817 info!("Running in db verification mode ...");
818 verify_server_core(&config).await;
819 }
820 KanidmdOpt::ShowReplicationCertificate => {
821 info!("Running show replication certificate ...");
822 let output_mode: ConsoleOutputMode = opt.output_mode.into();
823 submit_admin_req(
824 config.adminbindpath.as_str(),
825 AdminTaskRequest::ShowReplicationCertificate,
826 output_mode,
827 )
828 .await;
829 }
830 KanidmdOpt::RenewReplicationCertificate => {
831 info!("Running renew replication certificate ...");
832 let output_mode: ConsoleOutputMode = opt.output_mode.into();
833 submit_admin_req(
834 config.adminbindpath.as_str(),
835 AdminTaskRequest::RenewReplicationCertificate,
836 output_mode,
837 )
838 .await;
839 }
840 KanidmdOpt::RefreshReplicationConsumer { proceed } => {
841 info!("Running refresh replication consumer ...");
842 if !proceed {
843 error!("Unwilling to proceed. Check --help.");
844 } else {
845 let output_mode: ConsoleOutputMode = opt.output_mode.into();
846 submit_admin_req(
847 config.adminbindpath.as_str(),
848 AdminTaskRequest::RefreshReplicationConsumer,
849 output_mode,
850 )
851 .await;
852 }
853 }
854 KanidmdOpt::RecoverAccount { name } => {
855 info!("Running account recovery ...");
856 let output_mode: ConsoleOutputMode = opt.output_mode.into();
857 submit_admin_req(
858 config.adminbindpath.as_str(),
859 AdminTaskRequest::RecoverAccount {
860 name: name.to_owned(),
861 },
862 output_mode,
863 )
864 .await;
865 }
866 KanidmdOpt::DisableAccount { name } => {
867 info!("Running account disable ...");
868 let output_mode: ConsoleOutputMode = opt.output_mode.into();
869 submit_admin_req(
870 config.adminbindpath.as_str(),
871 AdminTaskRequest::DisableAccount {
872 name: name.to_owned(),
873 },
874 output_mode,
875 )
876 .await;
877 }
878 KanidmdOpt::Database {
879 commands: DbCommands::Reindex,
880 } => {
881 info!("Running in reindex mode ...");
882 reindex_server_core(&config).await;
883 }
884 KanidmdOpt::DbScan {
885 commands: DbScanOpt::ListIndexes,
886 } => {
887 info!("👀 db scan - list indexes");
888 dbscan_list_indexes_core(&config);
889 }
890 KanidmdOpt::DbScan {
891 commands: DbScanOpt::ListId2Entry,
892 } => {
893 info!("👀 db scan - list id2entry");
894 dbscan_list_id2entry_core(&config);
895 }
896 KanidmdOpt::DbScan {
897 commands: DbScanOpt::ListIndexAnalysis,
898 } => {
899 info!("👀 db scan - list index analysis");
900 dbscan_list_index_analysis_core(&config);
901 }
902 KanidmdOpt::DbScan {
903 commands: DbScanOpt::ListIndex(dopt),
904 } => {
905 info!("👀 db scan - list index content - {}", dopt.index_name);
906 dbscan_list_index_core(&config, dopt.index_name.as_str());
907 }
908 KanidmdOpt::DbScan {
909 commands: DbScanOpt::GetId2Entry(dopt),
910 } => {
911 info!("👀 db scan - get id2 entry - {}", dopt.id);
912 dbscan_get_id2entry_core(&config, dopt.id);
913 }
914
915 KanidmdOpt::DbScan {
916 commands: DbScanOpt::QuarantineId2Entry { id },
917 } => {
918 info!("☣️ db scan - quarantine id2 entry - {}", id);
919 dbscan_quarantine_id2entry_core(&config, *id);
920 }
921
922 KanidmdOpt::DbScan {
923 commands: DbScanOpt::ListQuarantined,
924 } => {
925 info!("☣️ db scan - list quarantined");
926 dbscan_list_quarantined_core(&config);
927 }
928
929 KanidmdOpt::DbScan {
930 commands: DbScanOpt::RestoreQuarantined { id },
931 } => {
932 info!("☣️ db scan - restore quarantined entry - {}", id);
933 dbscan_restore_quarantined_core(&config, *id);
934 }
935
936 KanidmdOpt::DomainSettings {
937 commands: DomainSettingsCmds::Change,
938 } => {
939 info!("Running in domain name change mode ... this may take a long time ...");
940 domain_rename_core(&config).await;
941 }
942
943 KanidmdOpt::DomainSettings {
944 commands: DomainSettingsCmds::Show,
945 } => {
946 info!("Running domain show ...");
947 let output_mode: ConsoleOutputMode = opt.output_mode.into();
948 submit_admin_req(
949 config.adminbindpath.as_str(),
950 AdminTaskRequest::DomainShow,
951 output_mode,
952 )
953 .await;
954 }
955
956 KanidmdOpt::DomainSettings {
957 commands: DomainSettingsCmds::UpgradeCheck,
958 } => {
959 info!("Running domain upgrade check ...");
960 let output_mode: ConsoleOutputMode = opt.output_mode.into();
961 submit_admin_req(
962 config.adminbindpath.as_str(),
963 AdminTaskRequest::DomainUpgradeCheck,
964 output_mode,
965 )
966 .await;
967 }
968
969 KanidmdOpt::DomainSettings {
970 commands: DomainSettingsCmds::Raise,
971 } => {
972 info!("Running domain raise ...");
973 let output_mode: ConsoleOutputMode = opt.output_mode.to_owned().into();
974 submit_admin_req(
975 config.adminbindpath.as_str(),
976 AdminTaskRequest::DomainRaise,
977 output_mode,
978 )
979 .await;
980 }
981
982 KanidmdOpt::DomainSettings {
983 commands: DomainSettingsCmds::Remigrate { level },
984 } => {
985 info!("⚠️ Running domain remigrate ...");
986 let output_mode: ConsoleOutputMode = opt.output_mode.into();
987 submit_admin_req(
988 config.adminbindpath.as_str(),
989 AdminTaskRequest::DomainRemigrate { level: *level },
990 output_mode,
991 )
992 .await;
993 }
994
995 KanidmdOpt::Database {
996 commands: DbCommands::Vacuum,
997 } => {
998 info!("Running in vacuum mode ...");
999 vacuum_server_core(&config);
1000 }
1001 KanidmdOpt::HealthCheck(sopt) => {
1002 debug!("{sopt:?}");
1003
1004 let healthcheck_url = match &sopt.check_origin {
1005 true => format!("{}/status", config.origin),
1006 false => {
1007 format!(
1009 "https://{}/status",
1010 config.address.replace("[::]", "localhost")
1011 )
1012 }
1013 };
1014
1015 info!("Checking {healthcheck_url}");
1016
1017 let mut client = reqwest::ClientBuilder::new()
1018 .danger_accept_invalid_certs(!sopt.verify_tls)
1019 .danger_accept_invalid_hostnames(!sopt.verify_tls)
1020 .https_only(true);
1021
1022 client = match &config.tls_config {
1023 None => client,
1024 Some(tls_config) => {
1025 debug!(
1026 "Trying to load {} to build a CA cert path",
1027 tls_config.chain.display()
1028 );
1029 let ca_cert_path = tls_config.chain.clone();
1031 match ca_cert_path.exists() {
1032 true => {
1033 let mut cert_buf = Vec::new();
1034 if let Err(err) = std::fs::File::open(&ca_cert_path)
1035 .and_then(|mut file| file.read_to_end(&mut cert_buf))
1036 {
1037 error!(
1038 "Failed to read {:?} from filesystem: {:?}",
1039 ca_cert_path, err
1040 );
1041 return ExitCode::FAILURE;
1042 }
1043
1044 let ca_chain_parsed =
1045 match reqwest::Certificate::from_pem_bundle(&cert_buf) {
1046 Ok(val) => val,
1047 Err(e) => {
1048 error!(
1049 "Failed to parse {:?} into CA chain!\nError: {:?}",
1050 ca_cert_path, e
1051 );
1052 return ExitCode::FAILURE;
1053 }
1054 };
1055
1056 for cert in ca_chain_parsed.into_iter().skip(1) {
1058 client = client.add_root_certificate(cert)
1059 }
1060 client
1061 }
1062 false => {
1063 warn!(
1064 "Couldn't find ca cert {} but carrying on...",
1065 tls_config.chain.display()
1066 );
1067 client
1068 }
1069 }
1070 }
1071 };
1072 #[allow(clippy::unwrap_used)]
1073 let client = client.build().unwrap();
1074
1075 let req = match client.get(&healthcheck_url).send().await {
1076 Ok(val) => val,
1077 Err(error) => {
1078 let error_message = {
1079 if error.is_timeout() {
1080 format!("Timeout connecting to url={healthcheck_url}")
1081 } else if error.is_connect() {
1082 format!("Connection failed: {error}")
1083 } else {
1084 format!("Failed to complete healthcheck: {error:?}")
1085 }
1086 };
1087 error!("CRITICAL: {error_message}");
1088 return ExitCode::FAILURE;
1089 }
1090 };
1091 debug!("Request: {req:?}");
1092 let output_mode: ConsoleOutputMode = opt.output_mode.to_owned().into();
1093 match output_mode {
1094 ConsoleOutputMode::JSON => {
1095 println!("{{\"result\":\"OK\"}}")
1096 }
1097 ConsoleOutputMode::Text => {
1098 info!("OK")
1099 }
1100 }
1101 }
1102 KanidmdOpt::Version => {}
1103 }
1104 ExitCode::SUCCESS
1105}