diff options
| author | konstpic <156318483+konstpic@users.noreply.github.com> | 2025-09-28 22:00:16 +0300 |
|---|---|---|
| committer | mhsanaei <ho3ein.sanaei@gmail.com> | 2025-09-28 22:04:54 +0300 |
| commit | 28a17a80ec0c4a0f82e8acfca351651d762b3ec9 (patch) | |
| tree | 7902b7b4cba04bce816ad17c9490f7228574a096 /util/ldap | |
| parent | 30565833889171afe5c934f97bc0e767534e8310 (diff) | |
feat: add ldap component (#3568)
* add ldap component
* fix: fix russian comments, tls cert verify default true
* feat: remove replaces go mod for local dev
Diffstat (limited to 'util/ldap')
| -rw-r--r-- | util/ldap/ldap.go | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/util/ldap/ldap.go b/util/ldap/ldap.go new file mode 100644 index 00000000..1c7a20e7 --- /dev/null +++ b/util/ldap/ldap.go @@ -0,0 +1,144 @@ +package ldaputil + +import ( + "crypto/tls" + "fmt" + + "github.com/go-ldap/ldap/v3" +) + +type Config struct { + Host string + Port int + UseTLS bool + BindDN string + Password string + BaseDN string + UserFilter string + UserAttr string + FlagField string + TruthyVals []string + Invert bool +} + +// FetchVlessFlags returns map[email]enabled +func FetchVlessFlags(cfg Config) (map[string]bool, error) { + addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) + var conn *ldap.Conn + var err error + if cfg.UseTLS { + conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false}) + } else { + conn, err = ldap.Dial("tcp", addr) + } + if err != nil { + return nil, err + } + defer conn.Close() + + if cfg.BindDN != "" { + if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil { + return nil, err + } + } + + if cfg.UserFilter == "" { + cfg.UserFilter = "(objectClass=person)" + } + if cfg.UserAttr == "" { + cfg.UserAttr = "mail" + } + // if field not set we fallback to legacy vless_enabled + if cfg.FlagField == "" { + cfg.FlagField = "vless_enabled" + } + + req := ldap.NewSearchRequest( + cfg.BaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + cfg.UserFilter, + []string{cfg.UserAttr, cfg.FlagField}, + nil, + ) + + res, err := conn.Search(req) + if err != nil { + return nil, err + } + + result := make(map[string]bool, len(res.Entries)) + for _, e := range res.Entries { + user := e.GetAttributeValue(cfg.UserAttr) + if user == "" { + continue + } + val := e.GetAttributeValue(cfg.FlagField) + enabled := false + for _, t := range cfg.TruthyVals { + if val == t { + enabled = true + break + } + } + if cfg.Invert { + enabled = !enabled + } + result[user] = enabled + } + return result, nil +} + +// AuthenticateUser searches user by cfg.UserAttr and attempts to bind with provided password. +func AuthenticateUser(cfg Config, username, password string) (bool, error) { + addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) + var conn *ldap.Conn + var err error + if cfg.UseTLS { + conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false}) + } else { + conn, err = ldap.Dial("tcp", addr) + } + if err != nil { + return false, err + } + defer conn.Close() + + // Optional initial bind for search + if cfg.BindDN != "" { + if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil { + return false, err + } + } + + if cfg.UserFilter == "" { + cfg.UserFilter = "(objectClass=person)" + } + if cfg.UserAttr == "" { + cfg.UserAttr = "uid" + } + + // Build filter to find specific user + filter := fmt.Sprintf("(&%s(%s=%s))", cfg.UserFilter, cfg.UserAttr, ldap.EscapeFilter(username)) + req := ldap.NewSearchRequest( + cfg.BaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false, + filter, + []string{"dn"}, + nil, + ) + res, err := conn.Search(req) + if err != nil { + return false, err + } + if len(res.Entries) == 0 { + return false, nil + } + userDN := res.Entries[0].DN + // Try to bind as the user + if err := conn.Bind(userDN, password); err != nil { + return false, nil + } + return true, nil +} + + |
