package ldap import ( "context" "fmt" "strings" ldaplib "github.com/go-ldap/ldap/v3" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/plugins/base" ) // LDAPConnection LDAP连接包装器 type LDAPConnection struct { client *ldaplib.Conn target string } // LDAPConnector LDAP连接器实现 type LDAPConnector struct { host string port string } // NewLDAPConnector 创建LDAP连接器 func NewLDAPConnector() *LDAPConnector { return &LDAPConnector{} } // Connect 连接到LDAP服务 func (c *LDAPConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) { c.host = info.Host c.port = info.Ports target := fmt.Sprintf("%s:%s", c.host, c.port) // 使用Context控制的TCP连接 conn, err := common.WrapperTcpWithContext(ctx, "tcp", target) if err != nil { return nil, fmt.Errorf("LDAP连接失败: %v", err) } // 创建LDAP连接 ldapConn := ldaplib.NewConn(conn, false) go ldapConn.Start() // 在goroutine中启动连接处理 return &LDAPConnection{ client: ldapConn, target: target, }, nil } // Authenticate 认证 func (c *LDAPConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error { ldapConn, ok := conn.(*LDAPConnection) if !ok { return fmt.Errorf("无效的连接类型") } // 在goroutine中进行认证操作,支持Context取消 resultChan := make(chan error, 1) go func() { var err error if cred.Username == "" && cred.Password == "" { // 匿名绑定 err = ldapConn.client.UnauthenticatedBind("") } else { // 构建绑定DN - 支持多种常见格式,优先管理员DN bindDNs := []string{ fmt.Sprintf("cn=%s,dc=example,dc=com", cred.Username), // 管理员绑定格式 fmt.Sprintf("cn=%s,ou=users,dc=example,dc=com", cred.Username), // 用户绑定格式 fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", cred.Username), fmt.Sprintf("uid=%s,dc=example,dc=com", cred.Username), cred.Username, // 直接使用用户名作为DN } // 尝试不同的绑定DN格式 var bindErr error for _, bindDN := range bindDNs { bindErr = ldapConn.client.Bind(bindDN, cred.Password) if bindErr == nil { break } } err = bindErr } // 绑定成功即表示认证成功,不需要额外搜索验证 // 因为某些LDAP配置下普通用户没有搜索权限 select { case resultChan <- err: case <-ctx.Done(): } }() // 等待认证结果或Context取消 select { case err := <-resultChan: if err != nil { // 检查是否是认证失败 if strings.Contains(strings.ToLower(err.Error()), "bind") || strings.Contains(strings.ToLower(err.Error()), "authentication") || strings.Contains(strings.ToLower(err.Error()), "invalid credentials") || strings.Contains(strings.ToLower(err.Error()), "49") { // LDAP错误码49表示认证失败 return fmt.Errorf("LDAP认证失败") } return fmt.Errorf("LDAP操作失败: %v", err) } return nil case <-ctx.Done(): return fmt.Errorf("LDAP认证超时: %v", ctx.Err()) } } // Close 关闭连接 func (c *LDAPConnector) Close(conn interface{}) error { if ldapConn, ok := conn.(*LDAPConnection); ok && ldapConn.client != nil { ldapConn.client.Close() } return nil }