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 std::collections::HashMap;
23use std::error::Error;
24use std::process;
25use std::io::{self, Write, stdin};
26use log::{info, debug, error, trace};
27
28pub async fn ldap_search(
30 ldaps: bool,
31 ip: &Option<String>,
32 port: &Option<u16>,
33 domain: &String,
34 ldapfqdn: &String,
35 username: &String,
36 password: &String,
37 kerberos: bool,
38 ldapfilter: &String,
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.len() <= 0 {
159 process::exit(0x0100);
160 }
161
162 ldap.unbind().await?;
164 }
165
166 return 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<String>,
183 port: &Option<u16>,
184 domain: &String,
185 ldapfqdn: &String,
186 username: &String,
187 password: &String,
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.contains("not set") && !kerberos {
200 print!("Username: ");
201 io::stdout().flush()?;
202 stdin().read_line(&mut s).expect("Did not enter a correct username");
203 io::stdout().flush()?;
204 if let Some('\n')=s.chars().next_back() {
205 s.pop();
206 }
207 if let Some('\r')=s.chars().next_back() {
208 s.pop();
209 }
210 _s_username = s.to_owned();
211 } else {
212 _s_username = username.to_owned();
213 }
214
215 let mut s_email: String = "".to_owned();
217 if !_s_username.contains("@") {
218 s_email.push_str(&_s_username.to_string());
219 s_email.push_str("@");
220 s_email.push_str(domain);
221 _s_username = s_email.to_string();
222 } else {
223 s_email = _s_username.to_string().to_lowercase();
224 }
225
226 let mut _s_password: String = String::new();
228 if !_s_username.contains("not set") && !kerberos {
229 if password.contains("not set") {
230 let rpass: String = rpassword::prompt_password("Password: ").unwrap_or("not set".to_string());
232 _s_password = rpass;
233 } else {
234 _s_password = password.to_owned();
235 }
236 } else {
237 _s_password = password.to_owned();
238 }
239
240 debug!("IP: {}", match ip {
242 Some(ip) => ip.to_owned(),
243 None => "not set".to_owned()
244 });
245 debug!("PORT: {}", match port {
246 Some(p) => {
247 p.to_string()
248 },
249 None => "not set".to_owned()
250 });
251 debug!("FQDN: {}", ldapfqdn);
252 debug!("Url: {}", s_url);
253 debug!("Domain: {}", domain);
254 debug!("Username: {}", _s_username);
255 debug!("Email: {}", s_email.to_lowercase());
256 debug!("Password: {}", _s_password);
257 debug!("DC: {:?}", s_dc);
258 debug!("Kerberos: {:?}", kerberos);
259
260 Ok(LdapArgs {
261 s_url: s_url.to_string(),
262 _s_dc: s_dc,
263 _s_email: s_email.to_string().to_lowercase(),
264 s_username: s_email.to_string().to_lowercase(),
265 s_password: _s_password.to_string(),
266 })
267}
268
269fn prepare_ldap_url(ldaps: bool, ip: &Option<String>, port: &Option<u16>, domain: &String) -> String {
271 let protocol = if ldaps || port.unwrap_or(0) == 636 {
272 "ldaps"
273 } else {
274 "ldap"
275 };
276
277 let target = match ip {
278 Some(ip) => ip,
279 None => domain
280 };
281
282 match port {
283 Some(port) => {
284 format!("{protocol}://{target}:{port}")
285 }
286 None => {
287 format!("{protocol}://{target}")
288 }
289 }
290}
291
292pub fn prepare_ldap_dc(domain: &String) -> Vec<String> {
294
295 let mut dc: String = "".to_owned();
296 let mut naming_context: Vec<String> = Vec::new();
297
298 if !domain.contains(".") {
300 dc.push_str("DC=");
301 dc.push_str(&domain);
302 naming_context.push(dc[..].to_string());
303 }
304 else
305 {
306 naming_context.push(domain_to_dc(domain));
307 }
308
309 naming_context.push(format!("{}{}","CN=Configuration,",dc[..].to_string()));
311
312 return naming_context
313}
314
315#[cfg(not(feature = "nogssapi"))]
317async fn gssapi_connection(
318 ldap: &mut ldap3::Ldap,
319 ldapfqdn: &String,
320 domain: &String,
321) -> Result<(), Box<dyn Error>> {
322 let res = ldap.sasl_gssapi_bind(ldapfqdn).await?.success();
323 match res {
324 Ok(_res) => {
325 info!("Connected to {} Active Directory!", domain.to_uppercase().bold().green());
326 info!("Starting data collection...");
327 },
328 Err(err) => {
329 error!("Failed to authenticate to {} Active Directory. Reason: {err}\n", domain.to_uppercase().bold().red());
330 process::exit(0x0100);
331 }
332 }
333 Ok(())
334}
335
336pub async fn get_all_naming_contexts(
338 ldap: &mut ldap3::Ldap
339) -> Result<Vec<String>, Box<dyn Error>> {
340 let adapters: Vec<Box<dyn Adapter<_,_>>> = vec![
342 Box::new(EntriesOnly::new()),
343 Box::new(PagedResults::new(999)),
344 ];
345
346 let mut search = ldap.streaming_search_with(
348 adapters,
349 "",
350 Scope::Base,
351 "(objectClass=*)",
352 vec!["namingContexts"],
353 ).await?;
354
355 let mut rs: Vec<SearchEntry> = Vec::new();
357 while let Some(entry) = search.next().await? {
358 let entry = SearchEntry::construct(entry);
359 rs.push(entry);
360 }
361 let res = search.finish().await.success();
362
363 let mut naming_contexts: Vec<String> = Vec::new();
365 match res {
366 Ok(_res) => {
367 debug!("All namingContexts collected!");
368 for result in rs {
369 let result_attrs: HashMap<String, Vec<String>> = result.attrs;
370
371 for (_key, value) in &result_attrs {
372 for naming_context in value {
373 debug!("namingContext found: {}",&naming_context.bold().green());
374 naming_contexts.push(naming_context.to_string());
375 }
376 }
377 }
378 return Ok(naming_contexts)
379 },
380 Err(err) => {
381 error!("No namingContexts found! Reason: {err}");
382 }
383 }
384 Ok(Vec::new())
386}