1
0

ldap.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. package ldaputil
  2. import (
  3. "crypto/tls"
  4. "fmt"
  5. "slices"
  6. "github.com/go-ldap/ldap/v3"
  7. )
  8. type Config struct {
  9. Host string
  10. Port int
  11. UseTLS bool
  12. InsecureSkipVerify bool
  13. BindDN string
  14. Password string
  15. BaseDN string
  16. UserFilter string
  17. UserAttr string
  18. FlagField string
  19. TruthyVals []string
  20. Invert bool
  21. }
  22. func tlsConfig(cfg Config) *tls.Config {
  23. return &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify}
  24. }
  25. // FetchVlessFlags returns map[email]enabled
  26. func FetchVlessFlags(cfg Config) (map[string]bool, error) {
  27. addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
  28. scheme := "ldap"
  29. if cfg.UseTLS {
  30. scheme = "ldaps"
  31. }
  32. ldapURL := fmt.Sprintf("%s://%s", scheme, addr)
  33. var opts []ldap.DialOpt
  34. if cfg.UseTLS {
  35. opts = append(opts, ldap.DialWithTLSConfig(tlsConfig(cfg)))
  36. }
  37. conn, err := ldap.DialURL(ldapURL, opts...)
  38. if err != nil {
  39. return nil, err
  40. }
  41. defer conn.Close()
  42. if cfg.BindDN != "" {
  43. if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil {
  44. return nil, err
  45. }
  46. }
  47. if cfg.UserFilter == "" {
  48. cfg.UserFilter = "(objectClass=person)"
  49. }
  50. if cfg.UserAttr == "" {
  51. cfg.UserAttr = "mail"
  52. }
  53. // if field not set we fallback to legacy vless_enabled
  54. if cfg.FlagField == "" {
  55. cfg.FlagField = "vless_enabled"
  56. }
  57. req := ldap.NewSearchRequest(
  58. cfg.BaseDN,
  59. ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
  60. cfg.UserFilter,
  61. []string{cfg.UserAttr, cfg.FlagField},
  62. nil,
  63. )
  64. res, err := conn.Search(req)
  65. if err != nil {
  66. return nil, err
  67. }
  68. result := make(map[string]bool, len(res.Entries))
  69. for _, e := range res.Entries {
  70. user := e.GetAttributeValue(cfg.UserAttr)
  71. if user == "" {
  72. continue
  73. }
  74. val := e.GetAttributeValue(cfg.FlagField)
  75. enabled := slices.Contains(cfg.TruthyVals, val)
  76. if cfg.Invert {
  77. enabled = !enabled
  78. }
  79. result[user] = enabled
  80. }
  81. return result, nil
  82. }
  83. // AuthenticateUser searches user by cfg.UserAttr and attempts to bind with provided password.
  84. func AuthenticateUser(cfg Config, username, password string) (bool, error) {
  85. addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
  86. scheme := "ldap"
  87. if cfg.UseTLS {
  88. scheme = "ldaps"
  89. }
  90. ldapURL := fmt.Sprintf("%s://%s", scheme, addr)
  91. var opts []ldap.DialOpt
  92. if cfg.UseTLS {
  93. opts = append(opts, ldap.DialWithTLSConfig(tlsConfig(cfg)))
  94. }
  95. conn, err := ldap.DialURL(ldapURL, opts...)
  96. if err != nil {
  97. return false, err
  98. }
  99. defer conn.Close()
  100. // Optional initial bind for search
  101. if cfg.BindDN != "" {
  102. if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil {
  103. return false, err
  104. }
  105. }
  106. if cfg.UserFilter == "" {
  107. cfg.UserFilter = "(objectClass=person)"
  108. }
  109. if cfg.UserAttr == "" {
  110. cfg.UserAttr = "uid"
  111. }
  112. // Build filter to find specific user
  113. filter := fmt.Sprintf("(&%s(%s=%s))", cfg.UserFilter, cfg.UserAttr, ldap.EscapeFilter(username))
  114. req := ldap.NewSearchRequest(
  115. cfg.BaseDN,
  116. ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false,
  117. filter,
  118. []string{"dn"},
  119. nil,
  120. )
  121. res, err := conn.Search(req)
  122. if err != nil {
  123. return false, err
  124. }
  125. if len(res.Entries) == 0 {
  126. return false, nil
  127. }
  128. userDN := res.Entries[0].DN
  129. // Try to bind as the user
  130. if err := conn.Bind(userDN, password); err != nil {
  131. return false, nil
  132. }
  133. return true, nil
  134. }