1use crate::banner::progress_bar;
15use crate::utils::format::domain_to_dc;
16
17use colored::Colorize;
18use indicatif::ProgressBar;
19use ldap3::adapters::{Adapter, EntriesOnly};
20use ldap3::{adapters::PagedResults, controls::RawControl, LdapConnAsync, LdapConnSettings};
21use ldap3::{Scope, SearchEntry};
22use log::{info, debug, error, trace};
23use std::io::{self, Write, stdin};
24use std::collections::HashMap;
25use std::error::Error;
26use std::process;
27
28pub async fn ldap_search(
30 ldaps: bool,
31 ip: Option<&str>,
32 port: &Option<u16>,
33 domain: &str,
34 ldapfqdn: &str,
35 username: Option<&str>,
36 password: Option<&str>,
37 kerberos: bool,
38 ldapfilter: &str,
39) -> Result<Vec<SearchEntry>, Box<dyn Error>> {
40 let ldap_args = ldap_constructor(ldaps, ip, port, domain, ldapfqdn, username, password, kerberos)?;
42
43 let consettings = LdapConnSettings::new().set_no_tls_verify(true);
45 let (conn, mut ldap) = LdapConnAsync::with_settings(consettings, &ldap_args.s_url).await?;
46 ldap3::drive!(conn);
47
48 if !kerberos {
49 debug!("Trying to connect with simple_bind() function (username:password)");
50 let res = ldap.simple_bind(&ldap_args.s_username, &ldap_args.s_password).await?.success();
51 match res {
52 Ok(_res) => {
53 info!("Connected to {} Active Directory!", domain.to_uppercase().bold().green());
54 info!("Starting data collection...");
55 },
56 Err(err) => {
57 error!("Failed to authenticate to {} Active Directory. Reason: {err}\n", domain.to_uppercase().bold().red());
58 process::exit(0x0100);
59 }
60 }
61 }
62 else
63 {
64 debug!("Trying to connect with sasl_gssapi_bind() function (kerberos session)");
65 if !&ldapfqdn.contains("not set") {
66 #[cfg(not(feature = "nogssapi"))]
67 gssapi_connection(&mut ldap,&ldapfqdn,&domain).await?;
68 #[cfg(feature = "nogssapi")]{
69 error!("Kerberos auth and GSSAPI not compatible with current os!");
70 process::exit(0x0100);
71 }
72 } else {
73 error!("Need Domain Controller FQDN to bind GSSAPI connection. Please use '{}'\n", "-f DC01.DOMAIN.LAB".bold());
74 process::exit(0x0100);
75 }
76 }
77
78 let mut rs: Vec<SearchEntry> = Vec::new();
80
81 let res = match get_all_naming_contexts(&mut ldap).await {
83 Ok(res) => {
84 trace!("naming_contexts: {:?}", &res);
85 res
86 },
87 Err(err) => {
88 error!("No namingContexts found! Reason: {err}\n");
89 process::exit(0x0100);
90 }
91 };
92
93 if res.iter().any(|s| s.contains("Configuration")) {
96 for cn in &res {
97 let ctrls = RawControl {
100 ctype: String::from("1.2.840.113556.1.4.801"),
101 crit: true,
102 val: Some(vec![48, 3, 2, 1, 5]),
103 };
104 ldap.with_controls(ctrls.to_owned());
105
106 info!("Ldap filter : {}", ldapfilter.bold().green());
116 let _s_filter = ldapfilter;
117
118 let adapters: Vec<Box<dyn Adapter<_,_>>> = vec![
120 Box::new(EntriesOnly::new()),
121 Box::new(PagedResults::new(999)),
122 ];
123
124 let mut search = ldap.streaming_search_with(
126 adapters, cn,
128 Scope::Subtree,
129 _s_filter,
130 vec!["*", "nTSecurityDescriptor"],
131 ).await?;
134
135 let pb = ProgressBar::new(1);
137 let mut count = 0;
138 while let Some(entry) = search.next().await? {
139 let entry = SearchEntry::construct(entry);
140 count += 1;
143 progress_bar(pb.to_owned(),"LDAP objects retrieved".to_string(),count,"#".to_string());
144 rs.push(entry);
146 }
147 pb.finish_and_clear();
148
149 let res = search.finish().await.success();
150 match res {
151 Ok(_res) => info!("All data collected for NamingContext {}",&cn.bold()),
152 Err(err) => {
153 error!("No data collected on {}! Reason: {err}",&cn.bold().red());
154 }
155 }
156 }
157 if rs.is_empty() {
159 process::exit(0x0100);
160 }
161
162 ldap.unbind().await?;
164 }
165
166 Ok(rs)
168}
169
170struct LdapArgs {
172 s_url: String,
173 _s_dc: Vec<String>,
174 _s_email: String,
175 s_username: String,
176 s_password: String,
177}
178
179fn ldap_constructor(
181 ldaps: bool,
182 ip: Option<&str>,
183 port: &Option<u16>,
184 domain: &str,
185 ldapfqdn: &str,
186 username: Option<&str>,
187 password: Option<&str>,
188 kerberos: bool,
189) -> Result<LdapArgs, Box<dyn Error>> {
190 let s_url = prepare_ldap_url(ldaps, ip, port, domain);
192
193 let s_dc = prepare_ldap_dc(domain);
195
196 let mut s = String::new();
198 let mut _s_username: String;
199 if username.is_none() && !kerberos {
200 print!("Username: ");
201 io::stdout().flush()?;
202 stdin()
203 .read_line(&mut s)
204 .expect("Did not enter a correct username");
205 io::stdout().flush()?;
206 if let Some('\n') = s.chars().next_back() {
207 s.pop();
208 }
209 if let Some('\r') = s.chars().next_back() {
210 s.pop();
211 }
212 _s_username = s.to_owned();
213 } else {
214 _s_username = username.unwrap_or("not set").to_owned();
215 }
216
217 let mut s_email: String = "".to_owned();
219 if !_s_username.contains("@") {
220 s_email.push_str(&_s_username.to_string());
221 s_email.push_str("@");
222 s_email.push_str(domain);
223 _s_username = s_email.to_string();
224 } else {
225 s_email = _s_username.to_string().to_lowercase();
226 }
227
228 let mut _s_password: String = String::new();
230 if !_s_username.contains("not set") && !kerberos {
231 _s_password = match password {
232 Some(p) => p.to_owned(),
233 None => rpassword::prompt_password("Password: ").unwrap_or("not set".to_string()),
234 };
235 } else {
236 _s_password = password.unwrap_or("not set").to_owned();
237 }
238
239 debug!("IP: {}", match ip {
241 Some(ip) => ip,
242 None => "not set"
243 });
244 debug!("PORT: {}", match port {
245 Some(p) => {
246 p.to_string()
247 },
248 None => "not set".to_owned()
249 });
250 debug!("FQDN: {}", ldapfqdn);
251 debug!("Url: {}", s_url);
252 debug!("Domain: {}", domain);
253 debug!("Username: {}", _s_username);
254 debug!("Email: {}", s_email.to_lowercase());
255 debug!("Password: {}", _s_password);
256 debug!("DC: {:?}", s_dc);
257 debug!("Kerberos: {:?}", kerberos);
258
259 Ok(LdapArgs {
260 s_url: s_url.to_string(),
261 _s_dc: s_dc,
262 _s_email: s_email.to_string().to_lowercase(),
263 s_username: s_email.to_string().to_lowercase(),
264 s_password: _s_password.to_string(),
265 })
266}
267
268fn prepare_ldap_url(
270 ldaps: bool,
271 ip: Option<&str>,
272 port: &Option<u16>,
273 domain: &str
274) -> String {
275 let protocol = if ldaps || port.unwrap_or(0) == 636 {
276 "ldaps"
277 } else {
278 "ldap"
279 };
280
281 let target = match ip {
282 Some(ip) => ip,
283 None => domain,
284 };
285
286 match port {
287 Some(port) => {
288 format!("{protocol}://{target}:{port}")
289 }
290 None => {
291 format!("{protocol}://{target}")
292 }
293 }
294}
295
296pub fn prepare_ldap_dc(domain: &str) -> Vec<String> {
298
299 let mut dc: String = "".to_owned();
300 let mut naming_context: Vec<String> = Vec::new();
301
302 if !domain.contains(".") {
304 dc.push_str("DC=");
305 dc.push_str(domain);
306 naming_context.push(dc[..].to_string());
307 }
308 else {
309 naming_context.push(domain_to_dc(domain));
310 }
311
312 naming_context.push(format!("{}{}", "CN=Configuration,", &dc[..]));
314 naming_context
315}
316
317#[cfg(not(feature = "nogssapi"))]
319async fn gssapi_connection(
320 ldap: &mut ldap3::Ldap,
321 ldapfqdn: &str,
322 domain: &str,
323) -> Result<(), Box<dyn Error>> {
324 let res = ldap.sasl_gssapi_bind(ldapfqdn).await?.success();
325 match res {
326 Ok(_res) => {
327 info!("Connected to {} Active Directory!", domain.to_uppercase().bold().green());
328 info!("Starting data collection...");
329 }
330 Err(err) => {
331 error!("Failed to authenticate to {} Active Directory. Reason: {err}\n", domain.to_uppercase().bold().red());
332 process::exit(0x0100);
333 }
334 }
335 Ok(())
336}
337
338pub async fn get_all_naming_contexts(
340 ldap: &mut ldap3::Ldap
341) -> Result<Vec<String>, Box<dyn Error>> {
342 let adapters: Vec<Box<dyn Adapter<_, _>>> = vec![
344 Box::new(EntriesOnly::new()),
345 Box::new(PagedResults::new(999)),
346 ];
347
348 let mut search = ldap.streaming_search_with(
350 adapters,
351 "",
352 Scope::Base,
353 "(objectClass=*)",
354 vec!["namingContexts"],
355 ).await?;
356
357 let mut rs: Vec<SearchEntry> = Vec::new();
359 while let Some(entry) = search.next().await? {
360 let entry = SearchEntry::construct(entry);
361 rs.push(entry);
362 }
363 let res = search.finish().await.success();
364
365 let mut naming_contexts: Vec<String> = Vec::new();
367 match res {
368 Ok(_res) => {
369 debug!("All namingContexts collected!");
370 for result in rs {
371 let result_attrs: HashMap<String, Vec<String>> = result.attrs;
372
373 for (_key, value) in &result_attrs {
374 for naming_context in value {
375 debug!("namingContext found: {}",&naming_context.bold().green());
376 naming_contexts.push(naming_context.to_string());
377 }
378 }
379 }
380 return Ok(naming_contexts)
381 }
382 Err(err) => {
383 error!("No namingContexts found! Reason: {err}");
384 }
385 }
386 Ok(Vec::new())
388}