using System.Net; using System.Net.NetworkInformation; using System.Runtime.InteropServices; using ADBulkTool.Models; using DnsClient; namespace ADBulkTool.Services; public static class DomainDiscovery { public static async Task DiscoverAsync(TimeSpan timeout) { var steps = new List(); try { var domain = await GetSystemDomainAsync(timeout); if (string.IsNullOrWhiteSpace(domain)) { return new DiscoveryResult(null, null, null, null, null, "Could not detect a DNS domain. Ensure the machine is on a domain network with a proper DNS configuration."); } steps.Add($"Detected domain: {domain}"); var searchBase = DomainToSearchBase(domain); steps.Add($"Derived search base: {searchBase}"); var dnsServers = GetDnsServers(); var dnsServer = dnsServers.FirstOrDefault(); steps.Add(dnsServer is not null ? $"DNS server found: {dnsServer}" : "No DNS server found, using system default resolver."); var ldapHost = await FindDomainControllerViaSrvAsync(domain, dnsServer, timeout); if (ldapHost is not null) { steps.Add($"Found DC via SRV record: {ldapHost}"); return new DiscoveryResult(domain, dnsServer, ldapHost, 636, searchBase, string.Join(" | ", steps)); } ldapHost = await FindDomainControllerViaLdapSrvAsync(domain, dnsServer, timeout); if (ldapHost is not null) { steps.Add($"Found DC via generic LDAP SRV: {ldapHost}"); return new DiscoveryResult(domain, dnsServer, ldapHost, 636, searchBase, string.Join(" | ", steps)); } var guessedHost = $"dc01.{domain}"; steps.Add($"SRV lookup failed. Guessed DC: {guessedHost}"); return new DiscoveryResult(domain, dnsServer, guessedHost, 636, searchBase, string.Join(" | ", steps)); } catch (Exception ex) { return new DiscoveryResult(null, null, null, null, null, $"Discovery failed: {ex.Message}"); } } public static List GetDnsServers() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return GetDnsServersWindows(); } return GetDnsServersLinux(); } public static string? DomainToSearchBase(string? domain) { if (string.IsNullOrWhiteSpace(domain)) { return null; } var parts = domain.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (parts.Length == 0) { return null; } return string.Join(",", parts.Select(p => $"DC={p}")); } private static async Task GetSystemDomainAsync(TimeSpan timeout) { try { using var cts = new CancellationTokenSource(timeout); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { var resolvDomain = await Task.Run(() => GetDomainFromResolvConf(), cts.Token); if (!string.IsNullOrWhiteSpace(resolvDomain)) { return resolvDomain; } } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { var winDomain = await Task.Run(() => GetDomainFromWindows(), cts.Token); if (!string.IsNullOrWhiteSpace(winDomain)) { return winDomain; } } var hostDomain = await Task.Run(() => GetDomainFromHostname(), cts.Token); if (!string.IsNullOrWhiteSpace(hostDomain)) { return hostDomain; } var ipDomain = await Task.Run(() => { try { return IPGlobalProperties.GetIPGlobalProperties().DomainName; } catch { return string.Empty; } }, cts.Token); if (!string.IsNullOrWhiteSpace(ipDomain) && !string.Equals(ipDomain, "localdomain", StringComparison.OrdinalIgnoreCase)) { return ipDomain.TrimEnd('.'); } return null; } catch { return null; } } private static List GetDnsServersWindows() { var servers = new List(); try { foreach (var ni in NetworkInterface.GetAllNetworkInterfaces()) { if (ni.OperationalStatus != OperationalStatus.Up) { continue; } var props = ni.GetIPProperties(); if (props is null) { continue; } foreach (var dns in props.DnsAddresses) { if (dns.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !servers.Contains(dns.ToString())) { servers.Add(dns.ToString()); } } } } catch { } return servers; } private static List GetDnsServersLinux() { var servers = new List(); try { var lines = File.ReadAllLines("/etc/resolv.conf"); foreach (var line in lines) { var trimmed = line.Trim(); if (trimmed.StartsWith("nameserver ", StringComparison.OrdinalIgnoreCase)) { var parts = trimmed.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (parts.Length >= 2 && IPAddress.TryParse(parts[1], out _)) { servers.Add(parts[1]); } } } } catch { } return servers; } private static string? GetDomainFromResolvConf() { try { var lines = File.ReadAllLines("/etc/resolv.conf"); foreach (var line in lines) { var trimmed = line.Trim(); if (trimmed.StartsWith("domain ", StringComparison.OrdinalIgnoreCase)) { var parts = trimmed.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (parts.Length >= 2 && !string.IsNullOrWhiteSpace(parts[1])) { return parts[1].TrimEnd('.'); } } if (trimmed.StartsWith("search ", StringComparison.OrdinalIgnoreCase)) { var parts = trimmed.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (parts.Length >= 2 && !string.IsNullOrWhiteSpace(parts[1])) { return parts[1].TrimEnd('.'); } } } } catch { } return null; } private static string? GetDomainFromWindows() { try { foreach (var ni in NetworkInterface.GetAllNetworkInterfaces()) { if (ni.OperationalStatus != OperationalStatus.Up) { continue; } var props = ni.GetIPProperties(); if (props is null) { continue; } var suffix = props.DnsSuffix; if (!string.IsNullOrWhiteSpace(suffix)) { return suffix.TrimEnd('.'); } } } catch { } #pragma warning disable CA1416 // Registry access is guarded by platform check try { using var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey( @"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters"); if (key is not null) { var domain = key.GetValue("Domain") as string; if (!string.IsNullOrWhiteSpace(domain)) { return domain.TrimEnd('.'); } var dhcpDomain = key.GetValue("DhcpDomain") as string; if (!string.IsNullOrWhiteSpace(dhcpDomain)) { return dhcpDomain.TrimEnd('.'); } } } catch { } #pragma warning restore CA1416 return null; } private static string? GetDomainFromHostname() { try { var hostname = Dns.GetHostEntry("localhost").HostName; var dotIndex = hostname.IndexOf('.'); if (dotIndex > 0 && dotIndex < hostname.Length - 1) { var domain = hostname[(dotIndex + 1)..].TrimEnd('.'); return string.IsNullOrWhiteSpace(domain) ? null : domain; } } catch { } try { var fullHostname = IPGlobalProperties.GetIPGlobalProperties().HostName; var dotIndex = fullHostname.IndexOf('.'); if (dotIndex > 0 && dotIndex < fullHostname.Length - 1) { var domain = fullHostname[(dotIndex + 1)..].TrimEnd('.'); return string.IsNullOrWhiteSpace(domain) ? null : domain; } } catch { } return null; } private static async Task FindDomainControllerViaSrvAsync(string domain, string? dnsServer, TimeSpan timeout) { try { var srvQuery = $"_ldap._tcp.dc._msdcs.{domain}"; var lookup = CreateLookup(dnsServer); using var cts = new CancellationTokenSource(timeout); var result = await lookup.QueryAsync(srvQuery, QueryType.SRV, QueryClass.IN, cts.Token); var record = result.Answers.SrvRecords().FirstOrDefault(); return record?.DomainName.Value.TrimEnd('.'); } catch { return null; } } private static async Task FindDomainControllerViaLdapSrvAsync(string domain, string? dnsServer, TimeSpan timeout) { try { var srvQuery = $"_ldap._tcp.{domain}"; var lookup = CreateLookup(dnsServer); using var cts = new CancellationTokenSource(timeout); var result = await lookup.QueryAsync(srvQuery, QueryType.SRV, QueryClass.IN, cts.Token); var record = result.Answers.SrvRecords().FirstOrDefault(); return record?.DomainName.Value.TrimEnd('.'); } catch { return null; } } private static LookupClient CreateLookup(string? dnsServer) { if (dnsServer is not null && IPAddress.TryParse(dnsServer, out var ip)) { return new LookupClient(new LookupClientOptions(ip) { UseCache = false, Timeout = TimeSpan.FromSeconds(5) }); } return new LookupClient(new LookupClientOptions { UseCache = false, Timeout = TimeSpan.FromSeconds(5) }); } }