|
|
@@ -1,144 +1,142 @@
|
|
|
package ldaputil
|
|
|
|
|
|
import (
|
|
|
- "crypto/tls"
|
|
|
- "fmt"
|
|
|
+ "crypto/tls"
|
|
|
+ "fmt"
|
|
|
|
|
|
- "github.com/go-ldap/ldap/v3"
|
|
|
+ "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
|
|
|
+ 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()
|
|
|
+ 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.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"
|
|
|
- }
|
|
|
+ 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,
|
|
|
- )
|
|
|
+ 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
|
|
|
- }
|
|
|
+ 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
|
|
|
+ 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()
|
|
|
+ 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
|
|
|
- }
|
|
|
- }
|
|
|
+ // 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"
|
|
|
- }
|
|
|
+ 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
|
|
|
+ // 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
|
|
|
}
|
|
|
-
|
|
|
-
|