A cloud-native LDAP server backed by a Consul datastore
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
6.2 KiB

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. ldap "bottin/ldapserver"
  6. message "github.com/lor00x/goldap/message"
  7. )
  8. // Compare request -------------------------
  9. func (server *Server) handleCompare(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) {
  10. state := s.(*State)
  11. r := m.GetCompareRequest()
  12. code, err := server.handleCompareInternal(state, &r)
  13. res := ldap.NewResponse(code)
  14. if err != nil {
  15. res.SetDiagnosticMessage(err.Error())
  16. }
  17. w.Write(message.CompareResponse(res))
  18. }
  19. func (server *Server) handleCompareInternal(state *State, r *message.CompareRequest) (int, error) {
  20. attr := string(r.Ava().AttributeDesc())
  21. expected := string(r.Ava().AssertionValue())
  22. dn, err := server.checkDN(string(r.Entry()), false)
  23. if err != nil {
  24. return ldap.LDAPResultInvalidDNSyntax, err
  25. }
  26. // Check permissions
  27. if !server.config.Acl.Check(&state.login, dn, "read", []string{attr}) {
  28. return ldap.LDAPResultInsufficientAccessRights, nil
  29. }
  30. // Do query
  31. exists, err := server.objectExists(dn)
  32. if err != nil {
  33. return ldap.LDAPResultOperationsError, err
  34. }
  35. if !exists {
  36. return ldap.LDAPResultNoSuchObject, fmt.Errorf("Not found: %s", dn)
  37. }
  38. values, err := server.getAttribute(dn, attr)
  39. if err != nil {
  40. return ldap.LDAPResultOperationsError, err
  41. }
  42. for _, v := range values {
  43. if valueMatch(attr, v, expected) {
  44. return ldap.LDAPResultCompareTrue, nil
  45. }
  46. }
  47. return ldap.LDAPResultCompareFalse, nil
  48. }
  49. // Search request -------------------------
  50. func (server *Server) handleSearch(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) {
  51. state := s.(*State)
  52. r := m.GetSearchRequest()
  53. code, err := server.handleSearchInternal(state, w, &r)
  54. res := ldap.NewResponse(code)
  55. if err != nil {
  56. res.SetDiagnosticMessage(err.Error())
  57. }
  58. if code != ldap.LDAPResultSuccess {
  59. server.logger.Printf("Failed to do search %#v (%s)", r, err)
  60. }
  61. w.Write(message.SearchResultDone(res))
  62. }
  63. func (server *Server) handleSearchInternal(state *State, w ldap.ResponseWriter, r *message.SearchRequest) (int, error) {
  64. baseObject, err := server.checkDN(string(r.BaseObject()), true)
  65. if err != nil {
  66. return ldap.LDAPResultInvalidDNSyntax, err
  67. }
  68. server.logger.Tracef("-- SEARCH REQUEST: --")
  69. server.logger.Tracef("Request BaseDn=%s", baseObject)
  70. server.logger.Tracef("Request Filter=%s", r.Filter())
  71. server.logger.Tracef("Request FilterString=%s", r.FilterString())
  72. server.logger.Tracef("Request Attributes=%s", r.Attributes())
  73. server.logger.Tracef("Request TimeLimit=%d", r.TimeLimit().Int())
  74. if !server.config.Acl.Check(&state.login, "read", baseObject, []string{}) {
  75. return ldap.LDAPResultInsufficientAccessRights, fmt.Errorf("Please specify a base object on which you have read rights")
  76. }
  77. baseObjectLevel := len(strings.Split(baseObject, ","))
  78. basePath, err := dnToConsul(baseObject)
  79. if err != nil {
  80. return ldap.LDAPResultInvalidDNSyntax, err
  81. }
  82. if r.Scope() == message.SearchRequestScopeBaseObject {
  83. basePath += "/attribute="
  84. } else {
  85. basePath += "/"
  86. }
  87. data, _, err := server.kv.List(basePath, nil)
  88. if err != nil {
  89. return ldap.LDAPResultOperationsError, err
  90. }
  91. entries, err := parseConsulResult(data)
  92. if err != nil {
  93. return ldap.LDAPResultOperationsError, err
  94. }
  95. server.logger.Tracef("in %s: %#v", basePath, data)
  96. server.logger.Tracef("%#v", entries)
  97. for dn, entry := range entries {
  98. if r.Scope() == message.SearchRequestScopeBaseObject {
  99. if dn != baseObject {
  100. continue
  101. }
  102. } else if r.Scope() == message.SearchRequestSingleLevel {
  103. objectLevel := len(strings.Split(dn, ","))
  104. if objectLevel != baseObjectLevel+1 {
  105. continue
  106. }
  107. }
  108. // Filter out if we don't match requested filter
  109. matched, err := applyFilter(entry, r.Filter())
  110. if err != nil {
  111. return ldap.LDAPResultUnwillingToPerform, err
  112. }
  113. if !matched {
  114. continue
  115. }
  116. // Filter out if user is not allowed to read this
  117. if !server.config.Acl.Check(&state.login, "read", dn, []string{}) {
  118. continue
  119. }
  120. e := ldap.NewSearchResultEntry(dn)
  121. for attr, val := range entry {
  122. // If attribute is not in request, exclude it from returned entry
  123. if len(r.Attributes()) > 0 {
  124. found := false
  125. for _, requested := range r.Attributes() {
  126. if string(requested) == "1.1" && len(r.Attributes()) == 1 {
  127. break
  128. }
  129. if string(requested) == "*" || strings.EqualFold(string(requested), attr) {
  130. found = true
  131. break
  132. }
  133. }
  134. if !found {
  135. continue
  136. }
  137. }
  138. // If we are not allowed to read attribute, exclude it from returned entry
  139. if !server.config.Acl.Check(&state.login, "read", dn, []string{attr}) {
  140. continue
  141. }
  142. // Send result
  143. for _, v := range val {
  144. e.AddAttribute(message.AttributeDescription(attr),
  145. message.AttributeValue(v))
  146. }
  147. }
  148. w.Write(e)
  149. }
  150. return ldap.LDAPResultSuccess, nil
  151. }
  152. func applyFilter(entry Entry, filter message.Filter) (bool, error) {
  153. if fAnd, ok := filter.(message.FilterAnd); ok {
  154. for _, cond := range fAnd {
  155. res, err := applyFilter(entry, cond)
  156. if err != nil {
  157. return false, err
  158. }
  159. if !res {
  160. return false, nil
  161. }
  162. }
  163. return true, nil
  164. } else if fOr, ok := filter.(message.FilterOr); ok {
  165. for _, cond := range fOr {
  166. res, err := applyFilter(entry, cond)
  167. if err != nil {
  168. return false, err
  169. }
  170. if res {
  171. return true, nil
  172. }
  173. }
  174. return false, nil
  175. } else if fNot, ok := filter.(message.FilterNot); ok {
  176. res, err := applyFilter(entry, fNot.Filter)
  177. if err != nil {
  178. return false, err
  179. }
  180. return !res, nil
  181. } else if fPresent, ok := filter.(message.FilterPresent); ok {
  182. what := string(fPresent)
  183. // Case insensitive search
  184. for desc, values := range entry {
  185. if strings.EqualFold(what, desc) {
  186. return len(values) > 0, nil
  187. }
  188. }
  189. return false, nil
  190. } else if fEquality, ok := filter.(message.FilterEqualityMatch); ok {
  191. desc := string(fEquality.AttributeDesc())
  192. target := string(fEquality.AssertionValue())
  193. // Case insensitive attribute search
  194. for entry_desc, value := range entry {
  195. if strings.EqualFold(entry_desc, desc) {
  196. for _, val := range value {
  197. if valueMatch(entry_desc, val, target) {
  198. return true, nil
  199. }
  200. }
  201. return false, nil
  202. }
  203. }
  204. return false, nil
  205. } else {
  206. return false, fmt.Errorf("Unsupported filter: %#v %T", filter, filter)
  207. }
  208. }