shadow1ng/fscan · error

LDAP dial: %w

Error message

LDAP dial: %w

What it means

connectToDomain in the Windows systeminfo plugin fails to open an LDAP connection (port 389) to the domain controller. It first tries ldap.DialURL with the DC hostname, and on failure retries once with the host's resolved IPv4; if both attempts fail, the underlying dial error is wrapped as "LDAP dial: %w". This means TCP to port 389 could not be established or the URL/handle was invalid.

Source

Thrown at plugins/local/systeminfo_dc_windows.go:91

func (p *SystemInfoPlugin) connectToDomain(domain string) (*domainInfo, error) {
	dcHost, err := p.findDC(domain)
	if err != nil {
		return nil, err
	}

	client, err := gssapi.NewSSPIClient()
	if err != nil {
		return nil, fmt.Errorf("SSPI: %w", err)
	}
	defer func() { _ = client.Close() }()

	conn, err := ldap.DialURL(ldapURL(dcHost, 389))
	if err != nil {
		if ipv4, resolveErr := resolveIPv4(dcHost); resolveErr == nil {
			conn, err = ldap.DialURL(ldapURL(ipv4, 389))
		}
		if err != nil {
			return nil, fmt.Errorf("LDAP dial: %w", err)
		}
	}

	if err := conn.GSSAPIBind(client, fmt.Sprintf("ldap/%s", dcHost), ""); err != nil {
		_ = conn.Close()
		return nil, fmt.Errorf("GSSAPI bind: %w", err)
	}

	baseDN, err := p.getBaseDN(conn, domain)
	if err != nil {
		_ = conn.Close()
		return nil, err
	}

	return &domainInfo{Domain: domain, BaseDN: baseDN, LDAPConn: conn}, nil
}

func (p *SystemInfoPlugin) findDC(domain string) (string, error) {

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Verify network reachability: from the scanning host run a TCP connect test to <dcHost>:389 (e.g. Test-NetConnection <dcHost> -Port 389 on Windows).
  2. Confirm DNS resolves dcHost; if not, add the DC's IP to hosts or fix DNS, since the resolveIPv4 fallback also depends on resolution.
  3. Check firewalls/ACLs between scanner and DC allow LDAP (TCP 389).
  4. Verify the DC host value passed into collectDomainInfo is correct and the DC is online (ping -n 1 <domain> is used later in findDC as a sanity check).

Example fix

// before (host unreachable)
conn, err := ldap.DialURL(ldapURL(dcHost, 389)) // fails: dial tcp 10.0.0.5:389: connectex: No connection could be made
// after (verify reachability first in caller or use correct host)
if !ldapReachable(dcHost, 389) {
    return nil, fmt.Errorf("skipping: DC %s not reachable on 389", dcHost)
}
conn, err := ldap.DialURL(ldapURL(dcHost, 389))
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check LDAP reachability before invoking the plugin
testConn, err := net.DialTimeout("tcp", net.JoinHostPort(dcHost, "389"), 5*time.Second)
if err != nil {
    return fmt.Errorf("DC %s:389 unreachable: %w", dcHost, err)
}
testConn.Close()

Try / catch

result := plugin.Scan(ctx, host, session)
if result != nil && !result.Success {
    var dnsErr *net.DNSError
    if errors.As(result.Error, &dnsErr) {
        // fix DNS or resolve IPv4 manually, then retry once
    } else if isTimeout(result.Error) {
        // retry with backoff
    }
}

Prevention

When it happens

Trigger: ldap.DialURL(ldapURL(dcHost, 389)) fails AND the fallback ldap.DialURL(ldapURL(ipv4, 389)) after resolveIPv4 also fails. Typical underlying causes: connection refused, timeout, DNS failure (also making resolveIPv4 fail), unreachable host, or malformed DC host value.

Common situations: Running the scanner from a machine that cannot reach the DC (firewall blocking LDAP 389), wrong or stale dcHost value, DNS not resolving the domain controller, DC offline, or scanning across a VPN/network segment where port 389 is filtered.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/e89204ae4c875509. Report an issue: GitHub.