mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
124 lines
3.2 KiB
Go
124 lines
3.2 KiB
Go
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
|
||
} |