ldap.go 3.5 KB

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