ldap.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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. BindDN string
  13. Password string
  14. BaseDN string
  15. UserFilter string
  16. UserAttr string
  17. FlagField string
  18. TruthyVals []string
  19. Invert bool
  20. }
  21. // FetchVlessFlags returns map[email]enabled
  22. func FetchVlessFlags(cfg Config) (map[string]bool, error) {
  23. addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
  24. scheme := "ldap"
  25. if cfg.UseTLS {
  26. scheme = "ldaps"
  27. }
  28. ldapURL := fmt.Sprintf("%s://%s", scheme, addr)
  29. var opts []ldap.DialOpt
  30. if cfg.UseTLS {
  31. opts = append(opts, ldap.DialWithTLSConfig(&tls.Config{
  32. InsecureSkipVerify: false,
  33. }))
  34. }
  35. conn, err := ldap.DialURL(ldapURL, opts...)
  36. if err != nil {
  37. return nil, err
  38. }
  39. defer conn.Close()
  40. if cfg.BindDN != "" {
  41. if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil {
  42. return nil, err
  43. }
  44. }
  45. if cfg.UserFilter == "" {
  46. cfg.UserFilter = "(objectClass=person)"
  47. }
  48. if cfg.UserAttr == "" {
  49. cfg.UserAttr = "mail"
  50. }
  51. // if field not set we fallback to legacy vless_enabled
  52. if cfg.FlagField == "" {
  53. cfg.FlagField = "vless_enabled"
  54. }
  55. req := ldap.NewSearchRequest(
  56. cfg.BaseDN,
  57. ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
  58. cfg.UserFilter,
  59. []string{cfg.UserAttr, cfg.FlagField},
  60. nil,
  61. )
  62. res, err := conn.Search(req)
  63. if err != nil {
  64. return nil, err
  65. }
  66. result := make(map[string]bool, len(res.Entries))
  67. for _, e := range res.Entries {
  68. user := e.GetAttributeValue(cfg.UserAttr)
  69. if user == "" {
  70. continue
  71. }
  72. val := e.GetAttributeValue(cfg.FlagField)
  73. enabled := slices.Contains(cfg.TruthyVals, val)
  74. if cfg.Invert {
  75. enabled = !enabled
  76. }
  77. result[user] = enabled
  78. }
  79. return result, nil
  80. }
  81. // AuthenticateUser searches user by cfg.UserAttr and attempts to bind with provided password.
  82. func AuthenticateUser(cfg Config, username, password string) (bool, error) {
  83. addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
  84. scheme := "ldap"
  85. if cfg.UseTLS {
  86. scheme = "ldaps"
  87. }
  88. ldapURL := fmt.Sprintf("%s://%s", scheme, addr)
  89. var opts []ldap.DialOpt
  90. if cfg.UseTLS {
  91. opts = append(opts, ldap.DialWithTLSConfig(&tls.Config{
  92. InsecureSkipVerify: false,
  93. }))
  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. }