From ade1591554a31b8a4a81e49342ccb23b022665e1 Mon Sep 17 00:00:00 2001 From: Firstyear Date: Mon, 24 Apr 2023 19:47:52 +1000 Subject: [PATCH] Consolidate unix tools (#1566) --- book/src/integrations/pam_and_nsswitch.md | 10 +- unix_integration/Cargo.toml | 19 +- unix_integration/build.rs | 42 +--- unix_integration/src/cache_clear.rs | 69 ------ unix_integration/src/cache_invalidate.rs | 67 ------ unix_integration/src/daemon_status.rs | 73 ------ unix_integration/src/opt/cache_clear.rs | 9 - unix_integration/src/opt/cache_invalidate.rs | 7 - unix_integration/src/opt/tool.rs | 49 +++++ unix_integration/src/opt/unixd_status.rs | 7 - unix_integration/src/test_auth.rs | 92 -------- unix_integration/src/tool.rs | 220 +++++++++++++++++++ 12 files changed, 281 insertions(+), 383 deletions(-) delete mode 100644 unix_integration/src/cache_clear.rs delete mode 100644 unix_integration/src/cache_invalidate.rs delete mode 100644 unix_integration/src/daemon_status.rs delete mode 100644 unix_integration/src/opt/cache_clear.rs delete mode 100644 unix_integration/src/opt/cache_invalidate.rs create mode 100644 unix_integration/src/opt/tool.rs delete mode 100644 unix_integration/src/opt/unixd_status.rs delete mode 100644 unix_integration/src/test_auth.rs create mode 100644 unix_integration/src/tool.rs diff --git a/book/src/integrations/pam_and_nsswitch.md b/book/src/integrations/pam_and_nsswitch.md index 9f8c539d0..409191f37 100644 --- a/book/src/integrations/pam_and_nsswitch.md +++ b/book/src/integrations/pam_and_nsswitch.md @@ -92,19 +92,19 @@ when first created. Defaults to false. You can then check the communication status of the daemon: ```bash -kanidm_unixd_status +kanidm-unix status ``` If the daemon is working, you should see: ``` -[2020-02-14T05:58:37Z INFO kanidm_unixd_status] working! +working! ``` If it is not working, you will see an error message: ``` -[2020-02-14T05:58:10Z ERROR kanidm_unixd_status] Error -> +[2020-02-14T05:58:10Z ERROR kanidm-unix] Error -> Os { code: 111, kind: ConnectionRefused, message: "Connection refused" } ``` @@ -473,13 +473,13 @@ cache_timeout = 60 You can invalidate the kanidm_unixd cache with: ```bash -kanidm_cache_invalidate +kanidm-unix cache-invalidate ``` You can clear (wipe) the cache with: ```bash -kanidm_cache_clear +kanidm-unix cache-clear ``` There is an important distinction between these two - invalidated cache items may still be yielded diff --git a/unix_integration/Cargo.toml b/unix_integration/Cargo.toml index d89fc5ebb..0ea623a1e 100644 --- a/unix_integration/Cargo.toml +++ b/unix_integration/Cargo.toml @@ -31,23 +31,8 @@ path = "src/ssh_authorizedkeys.rs" required-features = ["unix"] [[bin]] -name = "kanidm_cache_invalidate" -path = "src/cache_invalidate.rs" -required-features = ["unix"] - -[[bin]] -name = "kanidm_cache_clear" -path = "src/cache_clear.rs" -required-features = ["unix"] - -[[bin]] -name = "kanidm_unixd_status" -path = "src/daemon_status.rs" -required-features = ["unix"] - -[[bin]] -name = "kanidm_test_auth" -path = "src/test_auth.rs" +name = "kanidm-unix" +path = "src/tool.rs" required-features = ["unix"] [lib] diff --git a/unix_integration/build.rs b/unix_integration/build.rs index 4e0898aba..f361c8d3b 100644 --- a/unix_integration/build.rs +++ b/unix_integration/build.rs @@ -6,9 +6,7 @@ use clap::{IntoApp, Parser}; use clap_complete::{generate_to, Shell}; include!("src/opt/ssh_authorizedkeys.rs"); -include!("src/opt/cache_invalidate.rs"); -include!("src/opt/cache_clear.rs"); -include!("src/opt/unixd_status.rs"); +include!("src/opt/tool.rs"); fn main() { profiles::apply_profile(); @@ -45,46 +43,16 @@ fn main() { generate_to( Shell::Zsh, - &mut CacheInvalidateOpt::command(), - "kanidm_cache_invalidate", + &mut KanidmUnixParser::command(), + "kanidm_unix", comp_dir.clone(), ) .ok(); generate_to( Shell::Bash, - &mut CacheInvalidateOpt::command(), - "kanidm_cache_invalidate", + &mut KanidmUnixParser::command(), + "kanidm_unix", comp_dir.clone(), ) .ok(); - - generate_to( - Shell::Bash, - &mut CacheClearOpt::command(), - "kanidm_cache_clear", - comp_dir.clone(), - ) - .ok(); - generate_to( - Shell::Zsh, - &mut CacheClearOpt::command(), - "kanidm_cache_clear", - comp_dir.clone(), - ) - .ok(); - - generate_to( - Shell::Bash, - &mut UnixdStatusOpt::command(), - "kanidm_unixd_status", - comp_dir.clone(), - ) - .ok(); - generate_to( - Shell::Zsh, - &mut UnixdStatusOpt::command(), - "kanidm_unixd_status", - comp_dir, - ) - .ok(); } diff --git a/unix_integration/src/cache_clear.rs b/unix_integration/src/cache_clear.rs deleted file mode 100644 index ad390c0bb..000000000 --- a/unix_integration/src/cache_clear.rs +++ /dev/null @@ -1,69 +0,0 @@ -#![deny(warnings)] -#![warn(unused_extern_crates)] -#![deny(clippy::todo)] -#![deny(clippy::unimplemented)] -#![deny(clippy::unwrap_used)] -#![deny(clippy::expect_used)] -#![deny(clippy::panic)] -#![deny(clippy::unreachable)] -#![deny(clippy::await_holding_lock)] -#![deny(clippy::needless_pass_by_value)] -#![deny(clippy::trivially_copy_pass_by_ref)] - -#[macro_use] -extern crate tracing; - -use std::process::ExitCode; - -use clap::Parser; -use futures::executor::block_on; -use kanidm_unix_common::client::call_daemon; -use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH; -use kanidm_unix_common::unix_config::KanidmUnixdConfig; -use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse}; - -include!("./opt/cache_clear.rs"); - -#[tokio::main] -async fn main() -> ExitCode { - let opt = CacheClearOpt::parse(); - if opt.debug { - ::std::env::set_var("RUST_LOG", "kanidm=debug,kanidm_client=debug"); - } - if opt.version { - println!("{}", kanidm_proto::utils::get_version("kanidm_cache_clear")); - return ExitCode::SUCCESS; - } - sketching::tracing_subscriber::fmt::init(); - - debug!("Starting cache invalidate tool ..."); - - let cfg = match KanidmUnixdConfig::new().read_options_from_optional_config(DEFAULT_CONFIG_PATH) - { - Ok(c) => c, - Err(_e) => { - error!("Failed to parse {}", DEFAULT_CONFIG_PATH); - return ExitCode::FAILURE; - } - }; - - if !opt.really { - error!("Are you sure you want to proceed? If so use --really"); - return ExitCode::SUCCESS; - } - - let req = ClientRequest::ClearCache; - - match block_on(call_daemon(cfg.sock_path.as_str(), req)) { - Ok(r) => match r { - ClientResponse::Ok => info!("success"), - _ => { - error!("Error: unexpected response -> {:?}", r); - } - }, - Err(e) => { - error!("Error -> {:?}", e); - } - }; - ExitCode::SUCCESS -} diff --git a/unix_integration/src/cache_invalidate.rs b/unix_integration/src/cache_invalidate.rs deleted file mode 100644 index a5595a78b..000000000 --- a/unix_integration/src/cache_invalidate.rs +++ /dev/null @@ -1,67 +0,0 @@ -#![deny(warnings)] -#![warn(unused_extern_crates)] -#![deny(clippy::todo)] -#![deny(clippy::unimplemented)] -#![deny(clippy::unwrap_used)] -#![deny(clippy::expect_used)] -#![deny(clippy::panic)] -#![deny(clippy::unreachable)] -#![deny(clippy::await_holding_lock)] -#![deny(clippy::needless_pass_by_value)] -#![deny(clippy::trivially_copy_pass_by_ref)] - -#[macro_use] -extern crate tracing; - -use std::process::ExitCode; - -use clap::Parser; -use futures::executor::block_on; -use kanidm_unix_common::client::call_daemon; -use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH; -use kanidm_unix_common::unix_config::KanidmUnixdConfig; -use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse}; - -include!("./opt/cache_invalidate.rs"); - -#[tokio::main] -async fn main() -> ExitCode { - let opt = CacheInvalidateOpt::parse(); - if opt.debug { - ::std::env::set_var("RUST_LOG", "kanidm=debug,kanidm_client=debug"); - } - if opt.version { - println!( - "{}", - kanidm_proto::utils::get_version("kanidm_cache_invalidate") - ); - return ExitCode::SUCCESS; - } - sketching::tracing_subscriber::fmt::init(); - - debug!("Starting cache invalidate tool ..."); - - let cfg = match KanidmUnixdConfig::new().read_options_from_optional_config(DEFAULT_CONFIG_PATH) - { - Ok(c) => c, - Err(_e) => { - error!("Failed to parse {}", DEFAULT_CONFIG_PATH); - return ExitCode::FAILURE; - } - }; - - let req = ClientRequest::InvalidateCache; - - match block_on(call_daemon(cfg.sock_path.as_str(), req)) { - Ok(r) => match r { - ClientResponse::Ok => info!("success"), - _ => { - error!("Error: unexpected response -> {:?}", r); - } - }, - Err(e) => { - error!("Error -> {:?}", e); - } - }; - ExitCode::SUCCESS -} diff --git a/unix_integration/src/daemon_status.rs b/unix_integration/src/daemon_status.rs deleted file mode 100644 index 927ffb83a..000000000 --- a/unix_integration/src/daemon_status.rs +++ /dev/null @@ -1,73 +0,0 @@ -#![deny(warnings)] -#![warn(unused_extern_crates)] -#![deny(clippy::todo)] -#![deny(clippy::unimplemented)] -#![deny(clippy::unwrap_used)] -#![deny(clippy::expect_used)] -#![deny(clippy::panic)] -#![deny(clippy::unreachable)] -#![deny(clippy::await_holding_lock)] -#![deny(clippy::needless_pass_by_value)] -#![deny(clippy::trivially_copy_pass_by_ref)] - -#[macro_use] -extern crate tracing; - -use std::path::PathBuf; - -use clap::Parser; -// use futures::executor::block_on; -use kanidm_unix_common::client_sync::call_daemon_blocking; -use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH; -use kanidm_unix_common::unix_config::KanidmUnixdConfig; -use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse}; - -include!("./opt/unixd_status.rs"); - -fn main() { - let opt = UnixdStatusOpt::parse(); - if opt.debug { - ::std::env::set_var("RUST_LOG", "kanidm=debug,kanidm_client=debug"); - } - if opt.version { - println!( - "{}", - kanidm_proto::utils::get_version("kanidm_unixd_status") - ); - std::process::exit(0); - } - sketching::tracing_subscriber::fmt::init(); - - trace!("Starting cache status tool ..."); - - let cfg = match KanidmUnixdConfig::new().read_options_from_optional_config(DEFAULT_CONFIG_PATH) - { - Ok(c) => c, - Err(_e) => { - error!("Failed to parse {}", DEFAULT_CONFIG_PATH); - std::process::exit(1); - } - }; - - let req = ClientRequest::Status; - - let spath = PathBuf::from(cfg.sock_path.as_str()); - if !spath.exists() { - error!( - "kanidm_unixd socket {} does not exist - is the service running?", - cfg.sock_path - ) - } else { - match call_daemon_blocking(cfg.sock_path.as_str(), &req, cfg.unix_sock_timeout) { - Ok(r) => match r { - ClientResponse::Ok => println!("working!"), - _ => { - error!("Error: unexpected response -> {:?}", r); - } - }, - Err(e) => { - error!("Error -> {:?}", e); - } - } - } -} diff --git a/unix_integration/src/opt/cache_clear.rs b/unix_integration/src/opt/cache_clear.rs deleted file mode 100644 index fc5afc839..000000000 --- a/unix_integration/src/opt/cache_clear.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[derive(Debug, Parser)] -struct CacheClearOpt { - #[clap(short, long)] - debug: bool, - #[clap(short, long, action = clap::ArgAction::SetTrue)] - version: bool, - #[clap(long)] - really: bool, -} diff --git a/unix_integration/src/opt/cache_invalidate.rs b/unix_integration/src/opt/cache_invalidate.rs deleted file mode 100644 index eab5b5a82..000000000 --- a/unix_integration/src/opt/cache_invalidate.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[derive(Debug, Parser)] -struct CacheInvalidateOpt { - #[clap(short, long)] - debug: bool, - #[clap(short, long, action = clap::ArgAction::SetTrue)] - version: bool, -} diff --git a/unix_integration/src/opt/tool.rs b/unix_integration/src/opt/tool.rs new file mode 100644 index 000000000..d065d345c --- /dev/null +++ b/unix_integration/src/opt/tool.rs @@ -0,0 +1,49 @@ +use clap::Subcommand; + +#[derive(Debug, Subcommand)] +#[clap(about = "Kanidm Unixd Management Utility")] +pub enum KanidmUnixOpt { + /// Test authentication of a user via the unixd resolver "pam" channel. This does not + /// test that your pam configuration is correct - only that unixd is correctly processing + /// and validating authentications. + AuthTest { + #[clap(short, long)] + debug: bool, + #[clap(short = 'D', long = "name")] + account_id: String, + }, + /// Erase the content of the unixd resolver cache. You should probably use `invalidate` + /// instead. + CacheClear { + #[clap(short, long)] + debug: bool, + #[clap(long)] + really: bool, + }, + /// Invalidate, but don't erase the content of the unixd resolver cache. This will force + /// the unixd daemon to refresh all user and group content immediately. If the connection + /// is offline, entries will still be available and will be refreshed as soon as the daemon + /// is online again. + CacheInvalidate { + #[clap(short, long)] + debug: bool, + }, + /// Check that the unixd daemon is online and able to connect correctly to the kanidmd server. + Status { + #[clap(short, long)] + debug: bool, + }, + /// Show the version of this tool. + Version { + #[clap(short, long)] + debug: bool, + } +} + +#[derive(Debug, clap::Parser)] +#[clap(about = "Kanidm Unixd Management Utility")] +pub struct KanidmUnixParser { + #[clap(subcommand)] + pub commands: KanidmUnixOpt, +} + diff --git a/unix_integration/src/opt/unixd_status.rs b/unix_integration/src/opt/unixd_status.rs deleted file mode 100644 index cb3ee463b..000000000 --- a/unix_integration/src/opt/unixd_status.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[derive(Debug, Parser)] -struct UnixdStatusOpt { - #[clap(short, long)] - debug: bool, - #[clap(short, long, action = clap::ArgAction::SetTrue)] - version: bool, -} diff --git a/unix_integration/src/test_auth.rs b/unix_integration/src/test_auth.rs deleted file mode 100644 index 837f6e6fe..000000000 --- a/unix_integration/src/test_auth.rs +++ /dev/null @@ -1,92 +0,0 @@ -#![deny(warnings)] -#[macro_use] -extern crate tracing; - -use std::process::ExitCode; - -use clap::Parser; -use futures::executor::block_on; -use kanidm_unix_common::client::call_daemon; -use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH; -use kanidm_unix_common::unix_config::KanidmUnixdConfig; -use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse}; - -#[derive(Debug, Parser)] -struct ClientOpt { - #[clap(short, long)] - debug: bool, - #[clap(short = 'D', long = "name")] - account_id: String, -} - -#[tokio::main] -async fn main() -> ExitCode { - let opt = ClientOpt::parse(); - if opt.debug { - ::std::env::set_var("RUST_LOG", "kanidm=debug,kanidm_client=debug"); - } - sketching::tracing_subscriber::fmt::init(); - - debug!("Starting PAM auth tester tool ..."); - - let Ok(cfg) = KanidmUnixdConfig::new() - .read_options_from_optional_config(DEFAULT_CONFIG_PATH) - else { - error!("Failed to parse {}", DEFAULT_CONFIG_PATH); - return ExitCode::FAILURE - }; - - let password = match rpassword::prompt_password("Enter Unix password: ") { - Ok(p) => p, - Err(e) => { - error!("Problem getting input password: {}", e); - return ExitCode::FAILURE; - } - }; - - let req = ClientRequest::PamAuthenticate(opt.account_id.clone(), password); - let sereq = ClientRequest::PamAccountAllowed(opt.account_id); - - match block_on(call_daemon(cfg.sock_path.as_str(), req)) { - Ok(r) => match r { - ClientResponse::PamStatus(Some(true)) => { - info!("auth success!"); - } - ClientResponse::PamStatus(Some(false)) => { - info!("auth failed!"); - } - ClientResponse::PamStatus(None) => { - info!("user unknown"); - } - _ => { - // unexpected response. - error!("Error: unexpected response -> {:?}", r); - } - }, - Err(e) => { - error!("Error -> {:?}", e); - } - }; - - match block_on(call_daemon(cfg.sock_path.as_str(), sereq)) { - Ok(r) => match r { - ClientResponse::PamStatus(Some(true)) => { - info!("auth success!"); - } - ClientResponse::PamStatus(Some(false)) => { - info!("auth failed!"); - } - ClientResponse::PamStatus(None) => { - info!("user unknown"); - } - _ => { - // unexpected response. - error!("Error: unexpected response -> {:?}", r); - } - }, - Err(e) => { - error!("Error -> {:?}", e); - } - }; - ExitCode::SUCCESS -} diff --git a/unix_integration/src/tool.rs b/unix_integration/src/tool.rs new file mode 100644 index 000000000..74962a27b --- /dev/null +++ b/unix_integration/src/tool.rs @@ -0,0 +1,220 @@ +#![deny(warnings)] +#![warn(unused_extern_crates)] +#![deny(clippy::todo)] +#![deny(clippy::unimplemented)] +#![deny(clippy::unwrap_used)] +#![deny(clippy::expect_used)] +#![deny(clippy::panic)] +#![deny(clippy::unreachable)] +#![deny(clippy::await_holding_lock)] +#![deny(clippy::needless_pass_by_value)] +#![deny(clippy::trivially_copy_pass_by_ref)] + +#[macro_use] +extern crate tracing; + +use std::process::ExitCode; + +use clap::Parser; +use kanidm_unix_common::client::call_daemon; +use kanidm_unix_common::client_sync::call_daemon_blocking; +use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH; +use kanidm_unix_common::unix_config::KanidmUnixdConfig; +use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse}; +use std::path::PathBuf; + +include!("./opt/tool.rs"); + +#[tokio::main(flavor = "current_thread")] +async fn main() -> ExitCode { + let opt = KanidmUnixParser::parse(); + + let debug = match opt.commands { + KanidmUnixOpt::AuthTest { + debug, + account_id: _, + } => debug, + KanidmUnixOpt::CacheClear { debug, really: _ } => debug, + KanidmUnixOpt::CacheInvalidate { debug } => debug, + KanidmUnixOpt::Status { debug } => debug, + KanidmUnixOpt::Version { debug } => debug, + }; + + if debug { + ::std::env::set_var("RUST_LOG", "kanidm=debug,kanidm_client=debug"); + } + sketching::tracing_subscriber::fmt::init(); + + match opt.commands { + KanidmUnixOpt::AuthTest { + debug: _, + account_id, + } => { + debug!("Starting PAM auth tester tool ..."); + + let Ok(cfg) = KanidmUnixdConfig::new() + .read_options_from_optional_config(DEFAULT_CONFIG_PATH) + else { + error!("Failed to parse {}", DEFAULT_CONFIG_PATH); + return ExitCode::FAILURE + }; + + let password = match rpassword::prompt_password("Enter Unix password: ") { + Ok(p) => p, + Err(e) => { + error!("Problem getting input password: {}", e); + return ExitCode::FAILURE; + } + }; + + let req = ClientRequest::PamAuthenticate(account_id.clone(), password); + let sereq = ClientRequest::PamAccountAllowed(account_id); + + match call_daemon(cfg.sock_path.as_str(), req).await { + Ok(r) => match r { + ClientResponse::PamStatus(Some(true)) => { + println!("auth success!"); + } + ClientResponse::PamStatus(Some(false)) => { + println!("auth failed!"); + } + ClientResponse::PamStatus(None) => { + println!("auth user unknown"); + } + _ => { + // unexpected response. + error!("Error: unexpected response -> {:?}", r); + } + }, + Err(e) => { + error!("Error -> {:?}", e); + } + }; + + match call_daemon(cfg.sock_path.as_str(), sereq).await { + Ok(r) => match r { + ClientResponse::PamStatus(Some(true)) => { + println!("account success!"); + } + ClientResponse::PamStatus(Some(false)) => { + println!("account failed!"); + } + ClientResponse::PamStatus(None) => { + println!("account user unknown"); + } + _ => { + // unexpected response. + error!("Error: unexpected response -> {:?}", r); + } + }, + Err(e) => { + error!("Error -> {:?}", e); + } + }; + ExitCode::SUCCESS + } + KanidmUnixOpt::CacheClear { debug: _, really } => { + debug!("Starting cache clear tool ..."); + + let cfg = match KanidmUnixdConfig::new() + .read_options_from_optional_config(DEFAULT_CONFIG_PATH) + { + Ok(c) => c, + Err(_e) => { + error!("Failed to parse {}", DEFAULT_CONFIG_PATH); + return ExitCode::FAILURE; + } + }; + + if !really { + error!("Are you sure you want to proceed? If so use --really"); + return ExitCode::SUCCESS; + } + + let req = ClientRequest::ClearCache; + + match call_daemon(cfg.sock_path.as_str(), req).await { + Ok(r) => match r { + ClientResponse::Ok => info!("success"), + _ => { + error!("Error: unexpected response -> {:?}", r); + } + }, + Err(e) => { + error!("Error -> {:?}", e); + } + }; + println!("success"); + ExitCode::SUCCESS + } + KanidmUnixOpt::CacheInvalidate { debug: _ } => { + debug!("Starting cache invalidate tool ..."); + + let cfg = match KanidmUnixdConfig::new() + .read_options_from_optional_config(DEFAULT_CONFIG_PATH) + { + Ok(c) => c, + Err(_e) => { + error!("Failed to parse {}", DEFAULT_CONFIG_PATH); + return ExitCode::FAILURE; + } + }; + + let req = ClientRequest::InvalidateCache; + + match call_daemon(cfg.sock_path.as_str(), req).await { + Ok(r) => match r { + ClientResponse::Ok => info!("success"), + _ => { + error!("Error: unexpected response -> {:?}", r); + } + }, + Err(e) => { + error!("Error -> {:?}", e); + } + }; + println!("success"); + ExitCode::SUCCESS + } + KanidmUnixOpt::Status { debug: _ } => { + trace!("Starting cache status tool ..."); + + let cfg = match KanidmUnixdConfig::new() + .read_options_from_optional_config(DEFAULT_CONFIG_PATH) + { + Ok(c) => c, + Err(_e) => { + error!("Failed to parse {}", DEFAULT_CONFIG_PATH); + return ExitCode::FAILURE; + } + }; + + let req = ClientRequest::Status; + + let spath = PathBuf::from(cfg.sock_path.as_str()); + if !spath.exists() { + error!( + "kanidm_unixd socket {} does not exist - is the service running?", + cfg.sock_path + ) + } else { + match call_daemon_blocking(cfg.sock_path.as_str(), &req, cfg.unix_sock_timeout) { + Ok(r) => match r { + ClientResponse::Ok => println!("working!"), + _ => { + error!("Error: unexpected response -> {:?}", r); + } + }, + Err(e) => { + error!("Error -> {:?}", e); + } + } + } + ExitCode::SUCCESS + } + KanidmUnixOpt::Version { debug: _ } => { + println!("{}", kanidm_proto::utils::get_version("kanidm-unix")); + ExitCode::SUCCESS + } + } +}