Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
authorkonstpic <156318483+konstpic@users.noreply.github.com>2025-09-28 22:00:16 +0300
committermhsanaei <ho3ein.sanaei@gmail.com>2025-09-28 22:04:54 +0300
commit28a17a80ec0c4a0f82e8acfca351651d762b3ec9 (patch)
tree7902b7b4cba04bce816ad17c9490f7228574a096 /util
parent30565833889171afe5c934f97bc0e767534e8310 (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')
-rw-r--r--util/ldap/ldap.go144
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
+}
+
+