From abb4c7391ea4db419b19d947655ceee25d7585f8 Mon Sep 17 00:00:00 2001 From: candifloss Date: Thu, 24 Sep 2026 12:03:54 +0530 Subject: [PATCH] Basic ldap search --- .gitignore | 3 +- Cargo.toml | 13 ++++--- tools/lk-search/Cargo.toml | 2 +- tools/lk-search/src/main.rs | 70 +++++++++++++++++++++++++++++++++++-- 4 files changed, 80 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index aed0be4..3ffd906 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ Cargo.lock #.idea/ # Manually added -/scratchpad/ \ No newline at end of file +/scratchpad/ +dprint.json diff --git a/Cargo.toml b/Cargo.toml index 06b8396..ec8517b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,12 @@ [workspace] resolver = "3" members = [ - "crates/config","crates/ldap","crates/password", - "tools/lk-pswdreset", "tools/lk-search" + "crates/config", + "crates/ldap", + "crates/password", + + "tools/lk-pswdreset", + "tools/lk-search", ] [workspace.package] @@ -12,7 +16,8 @@ repository = "https://git.candifloss.cc/candifloss/ldap-kit.git" [workspace.dependencies] anyhow = "1.0.104" -clap = { version = "4.6.4", features = ["derive"] } +clap = { version = "4.6.7", features = ["derive"] } ldap3 = "0.12.1" serde = { version = "1.0.229", features = ["derive"] } -toml = "1.1.4" +tokio = { version = "1.53.1", features = ["macros", "rt-multi-thread"] } +toml = "1.1.6" diff --git a/tools/lk-search/Cargo.toml b/tools/lk-search/Cargo.toml index a0bd9a5..26dc4d3 100644 --- a/tools/lk-search/Cargo.toml +++ b/tools/lk-search/Cargo.toml @@ -8,5 +8,5 @@ repository.workspace = true [dependencies] anyhow.workspace = true clap.workspace = true +config = { path = "../../crates/config" } ldap3.workspace = true -config = { path = "../../crates/config" } \ No newline at end of file diff --git a/tools/lk-search/src/main.rs b/tools/lk-search/src/main.rs index e7a11a9..0724b1f 100644 --- a/tools/lk-search/src/main.rs +++ b/tools/lk-search/src/main.rs @@ -1,3 +1,69 @@ -fn main() { - println!("Hello, world!"); +use anyhow::{Result, bail}; +use clap::{ArgGroup, Parser}; +use ldap3::{LdapConn, Scope, SearchEntry}; + +use config::ConnectionConfig; + +#[derive(Debug, Parser)] +#[command(name = "lk-search", about = "Search an LDAP user")] +#[command(group( + ArgGroup::new("user") + .required(true) + .args(["username", "id_number"]) +))] +struct Args { + /// Search by username. + #[arg(short = 'u', long = "username", value_name = "USERNAME")] + username: Option, + + /// Search by ID number. + #[arg(short = 'n', long = "id-number", value_name = "ID")] + id_number: Option, +} + +fn main() -> Result<()> { + let args = Args::parse(); + let config = ConnectionConfig::load()?; + + let (search_attribute, search_value) = match (&args.username, &args.id_number) { + (Some(username), None) => ("uid", username), + (None, Some(id_number)) => ("uidNumber", id_number), + _ => unreachable!(), + }; + + let filter = format!("({search_attribute}={search_value})"); + + let mut ldap = LdapConn::new(&config.ldap.url)?; + + ldap.simple_bind(&config.read.bind_dn, &config.read.bind_pw)? + .success()?; + + let (entries, _result) = ldap + .search(&config.ldap.base_dn, Scope::Subtree, &filter, vec!["*"])? + .success()?; + + if entries.is_empty() { + bail!("User not found."); + } + + if entries.len() > 1 { + bail!( + "Search returned {} entries. Expected exactly one.", + entries.len() + ); + } + + let entry = SearchEntry::construct(entries.into_iter().next().unwrap()); + + println!("DN: {}", entry.dn); + + for (attribute, values) in entry.attrs { + println!("{attribute}:"); + + for value in values { + println!(" {value}"); + } + } + + Ok(()) }