diff --git a/main.go b/main.go index d5dcdb4..0156a6d 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,6 @@ import ( "math/rand" "os" "os/signal" - "strings" "syscall" ldap "./ldapserver" @@ -374,661 +373,4 @@ func (server *Server) handleBindInternal(state *State, r *message.BindRequest) ( return ldap.LDAPResultInvalidCredentials, nil } -func (server *Server) handleSearch(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) { - state := s.(*State) - r := m.GetSearchRequest() - code, err := server.handleSearchInternal(state, w, &r) - - res := ldap.NewResponse(code) - if err != nil { - res.SetDiagnosticMessage(err.Error()) - } - w.Write(message.SearchResultDone(res)) -} - -func (server *Server) handleSearchInternal(state *State, w ldap.ResponseWriter, r *message.SearchRequest) (int, error) { - if DEBUG { - server.logger.Printf("-- SEARCH REQUEST: --") - server.logger.Printf("Request BaseDn=%s", r.BaseObject()) - server.logger.Printf("Request Filter=%s", r.Filter()) - server.logger.Printf("Request FilterString=%s", r.FilterString()) - server.logger.Printf("Request Attributes=%s", r.Attributes()) - server.logger.Printf("Request TimeLimit=%d", r.TimeLimit().Int()) - } - - if !server.config.Acl.Check(&state.login, "read", string(r.BaseObject()), []string{}) { - return ldap.LDAPResultInsufficientAccessRights, fmt.Errorf("Please specify a base object on which you have read rights") - } - - baseObject, err := server.checkSuffix(string(r.BaseObject()), true) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } - basePath, err := dnToConsul(baseObject) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } - - data, _, err := server.kv.List(basePath+"/", nil) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - - entries, err := parseConsulResult(data) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - if DEBUG { - server.logger.Printf("in %s: %#v", basePath+"/", data) - server.logger.Printf("%#v", entries) - } - - for dn, entry := range entries { - // Filter out if we don't match requested filter - matched, err := applyFilter(entry, r.Filter()) - if err != nil { - return ldap.LDAPResultUnwillingToPerform, err - } - if !matched { - continue - } - - // Filter out if user is not allowed to read this - if !server.config.Acl.Check(&state.login, "read", dn, []string{}) { - continue - } - - e := ldap.NewSearchResultEntry(dn) - for attr, val := range entry { - // If attribute is not in request, exclude it from returned entry - if len(r.Attributes()) > 0 { - found := false - for _, requested := range r.Attributes() { - if strings.EqualFold(string(requested), attr) { - found = true - break - } - } - if !found { - continue - } - } - // If we are not allowed to read attribute, exclude it from returned entry - if !server.config.Acl.Check(&state.login, "read", dn, []string{attr}) { - continue - } - // Send result - for _, v := range val { - e.AddAttribute(message.AttributeDescription(attr), - message.AttributeValue(v)) - } - } - w.Write(e) - } - - return ldap.LDAPResultSuccess, nil -} - -func applyFilter(entry Entry, filter message.Filter) (bool, error) { - if fAnd, ok := filter.(message.FilterAnd); ok { - for _, cond := range fAnd { - res, err := applyFilter(entry, cond) - if err != nil { - return false, err - } - if !res { - return false, nil - } - } - return true, nil - } else if fOr, ok := filter.(message.FilterOr); ok { - for _, cond := range fOr { - res, err := applyFilter(entry, cond) - if err != nil { - return false, err - } - if res { - return true, nil - } - } - return false, nil - } else if fNot, ok := filter.(message.FilterNot); ok { - res, err := applyFilter(entry, fNot.Filter) - if err != nil { - return false, err - } - return !res, nil - } else if fPresent, ok := filter.(message.FilterPresent); ok { - what := string(fPresent) - // Case insensitive search - for desc, values := range entry { - if strings.EqualFold(what, desc) { - return len(values) > 0, nil - } - } - return false, nil - } else if fEquality, ok := filter.(message.FilterEqualityMatch); ok { - desc := string(fEquality.AttributeDesc()) - target := string(fEquality.AssertionValue()) - // Case insensitive attribute search - for entry_desc, value := range entry { - if strings.EqualFold(entry_desc, desc) { - for _, val := range value { - if val == target { - return true, nil - } - } - return false, nil - } - } - return false, nil - } else { - return false, fmt.Errorf("Unsupported filter: %#v %T", filter, filter) - } -} - -func (server *Server) handleAdd(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) { - state := s.(*State) - r := m.GetAddRequest() - - code, err := server.handleAddInternal(state, &r) - - res := ldap.NewResponse(code) - if err != nil { - res.SetDiagnosticMessage(err.Error()) - } - if code == ldap.LDAPResultSuccess { - server.logger.Printf("Successfully added %s", string(r.Entry())) - } else { - server.logger.Printf("Failed to add %s (%s)", string(r.Entry()), err) - } - w.Write(message.AddResponse(res)) -} - -func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (int, error) { - dn := string(r.Entry()) - - _, err := server.checkSuffix(dn, false) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } - - dnSplit, err := parseDN(dn) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } - - // Check permissions - attrListStr := []string{} - for _, attribute := range r.Attributes() { - attrListStr = append(attrListStr, string(attribute.Type_())) - } - if !server.config.Acl.Check(&state.login, "add", dn, attrListStr) { - return ldap.LDAPResultInsufficientAccessRights, nil - } - - // Check that object does not already exist - exists, err := server.objectExists(dn) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - if exists { - return ldap.LDAPResultEntryAlreadyExists, nil - } - - // Add object - - // If adding a group, track of who the members will be so that their memberOf field can be updated later - var members []string = nil - - entry := Entry{} - for _, attribute := range r.Attributes() { - key := string(attribute.Type_()) - vals_str := []string{} - for _, val := range attribute.Vals() { - vals_str = append(vals_str, string(val)) - } - - // Fail if they are trying to write memberOf, we manage this ourselves - err = checkRestrictedAttr(key) - if err != nil { - return ldap.LDAPResultObjectClassViolation, err - } - // If they are writing a member key, we have to check they are adding valid members - if strings.EqualFold(key, "member") { - members = vals_str - for _, member := range members { - _, err := server.checkSuffix(member, false) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } - exists, err = server.objectExists(member) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - if !exists { - return ldap.LDAPResultNoSuchObject, fmt.Errorf( - "Cannot add %s to members, it does not exist!", - member) - } - } - } - entry[key] = vals_str - } - - entry[ATTR_CREATORSNAME] = []string{state.login.user} - entry[ATTR_CREATETIMESTAMP] = []string{genTimestamp()} - entry[ATTR_ENTRYUUID] = []string{genUuid()} - entry[dnSplit[0].Type] = []string{dnSplit[0].Value} - - err = server.addElements(dn, entry) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - - if members != nil { - for _, member := range members { - memberGroups, err := server.getAttribute(member, ATTR_MEMBEROF) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - if memberGroups == nil { - memberGroups = []string{} - } - - alreadyMember := false - for _, mb := range memberGroups { - if mb == dn { - alreadyMember = true - server.logger.Printf("Warning: inconsistency detected, %s was memberOf %s at a time when it didn't exist!", - member, dn) - break - } - } - - if !alreadyMember { - memberGroups = append(memberGroups, dn) - err = server.addElements(member, Entry{ - ATTR_MEMBEROF: memberGroups, - }) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - } - } - } - - return ldap.LDAPResultSuccess, nil -} - -func (server *Server) handleCompare(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) { - state := s.(*State) - r := m.GetCompareRequest() - - code, err := server.handleCompareInternal(state, &r) - - res := ldap.NewResponse(code) - if err != nil { - res.SetDiagnosticMessage(err.Error()) - } - w.Write(message.CompareResponse(res)) -} - -func (server *Server) handleCompareInternal(state *State, r *message.CompareRequest) (int, error) { - dn := string(r.Entry()) - attr := string(r.Ava().AttributeDesc()) - expected := string(r.Ava().AssertionValue()) - - _, err := server.checkSuffix(dn, false) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } - - // Check permissions - if !server.config.Acl.Check(&state.login, dn, "read", []string{attr}) { - return ldap.LDAPResultInsufficientAccessRights, nil - } - - // Do query - exists, err := server.objectExists(dn) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - if !exists { - return ldap.LDAPResultNoSuchObject, fmt.Errorf("Not found: %s", dn) - } - - values, err := server.getAttribute(dn, attr) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - - for _, v := range values { - if v == expected { - return ldap.LDAPResultCompareTrue, nil - } - } - - return ldap.LDAPResultCompareFalse, nil -} - -func (server *Server) handleDelete(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) { - state := s.(*State) - r := m.GetDeleteRequest() - - code, err := server.handleDeleteInternal(state, &r) - - res := ldap.NewResponse(code) - if err != nil { - res.SetDiagnosticMessage(err.Error()) - } - if code == ldap.LDAPResultSuccess { - server.logger.Printf("Successfully deleted %s", string(r)) - } else { - server.logger.Printf("Failed to delete %s (%s)", string(r), err) - } - w.Write(message.DelResponse(res)) -} - -func (server *Server) handleDeleteInternal(state *State, r *message.DelRequest) (int, error) { - dn := string(*r) - - _, err := server.checkSuffix(dn, false) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } - - // Check for delete permission - if !server.config.Acl.Check(&state.login, "delete", dn, []string{}) { - return ldap.LDAPResultInsufficientAccessRights, nil - } - - // Check that this LDAP entry exists and has no children - path, err := dnToConsul(dn) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } - - items, _, err := server.kv.List(path+"/", nil) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - - if len(items) == 0 { - return ldap.LDAPResultNoSuchObject, fmt.Errorf("Not found: %s", dn) - } - for _, item := range items { - itemDN, _, err := consulToDN(item.Key) - if err != nil { - continue - } - if itemDN != dn { - return ldap.LDAPResultNotAllowedOnNonLeaf, fmt.Errorf( - "Cannot delete %d as it has children", dn) - } - } - - // Retrieve group membership before we delete everything - memberOf, err := server.getAttribute(dn, ATTR_MEMBEROF) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - - // Delete the LDAP entry - _, err = server.kv.DeleteTree(path+"/", nil) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - - // Delete it from the member list of all the groups it was a member of - if memberOf != nil { - for _, group := range memberOf { - groupMembers, err := server.getAttribute(dn, "member") - if err != nil { - return ldap.LDAPResultOperationsError, err - } - - newMembers := []string{} - for _, memb := range groupMembers { - if memb != dn { - newMembers = append(newMembers, memb) - } - } - - err = server.addElements(group, Entry{ - "member": newMembers, - }) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - } - } - - return ldap.LDAPResultSuccess, nil -} - -func (server *Server) handleModify(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) { - state := s.(*State) - r := m.GetModifyRequest() - - code, err := server.handleModifyInternal(state, &r) - - res := ldap.NewResponse(code) - if err != nil { - res.SetDiagnosticMessage(err.Error()) - } - if code == ldap.LDAPResultSuccess { - server.logger.Printf("Successfully modified %s", string(r.Object())) - } else { - server.logger.Printf("Failed to modifiy %s (%s)", string(r.Object()), err) - } - w.Write(message.ModifyResponse(res)) -} - -func (server *Server) handleModifyInternal(state *State, r *message.ModifyRequest) (int, error) { - dn := string(r.Object()) - - _, err := server.checkSuffix(dn, false) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } - - dnSplit, err := parseDN(dn) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } - - // First permission check with no particular attributes - if !server.config.Acl.Check(&state.login, "modify", dn, []string{}) { - return ldap.LDAPResultInsufficientAccessRights, nil - } - - // Retrieve previous values (by the way, check object exists) - path, err := dnToConsul(dn) - if err != nil { - return ldap.LDAPResultInvalidDNSyntax, err - } - - items, _, err := server.kv.List(path+"/attribute=", nil) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - - if len(items) == 0 { - return ldap.LDAPResultNoSuchObject, fmt.Errorf("Not found: %s", dn) - } - - prevEntry := Entry{} - for _, item := range items { - itemDN, attr, err := consulToDN(item.Key) - if err != nil { - continue - } - if itemDN != dn { - panic("itemDN != dn in handleModifyInternal") - } - vals, err := parseValue(item.Value) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - prevEntry[attr] = vals - } - - // Keep track of group members added/deleted - addMembers, delMembers := []string{}, []string{} - - // Produce new entry values to be saved - newEntry := Entry{} - for _, change := range r.Changes() { - attr := string(change.Modification().Type_()) - values := change.Modification().Vals() - - err = checkRestrictedAttr(attr) - if err != nil { - return ldap.LDAPResultObjectClassViolation, err - } - if strings.EqualFold(attr, dnSplit[0].Type) { - return ldap.LDAPResultObjectClassViolation, fmt.Errorf("%s may not be changed as it is part of object path", attr) - } - - // Check for permission to modify this attribute - if !server.config.Acl.Check(&state.login, "modify", dn, []string{attr}) { - return ldap.LDAPResultInsufficientAccessRights, nil - } - - if change.Operation() == ldap.ModifyRequestChangeOperationAdd { - newEntry[attr] = prevEntry[attr] - for _, val := range values { - present := false - for _, prevVal := range newEntry[attr] { - if prevVal == string(val) { - present = true - break - } - } - if !present { - newEntry[attr] = append(newEntry[attr], string(val)) - if strings.EqualFold(attr, "member") { - addMembers = append(addMembers, string(val)) - } - } - } - } else if change.Operation() == ldap.ModifyRequestChangeOperationDelete { - if len(values) == 0 { - // Delete everything - newEntry[attr] = []string{} - if strings.EqualFold(attr, "member") { - delMembers = append(delMembers, prevEntry[attr]...) - } - } else { - // Delete only those specified - newEntry[attr] = []string{} - for _, prevVal := range prevEntry[attr] { - keep := true - for _, delVal := range values { - if string(delVal) == prevVal { - keep = false - break - } - } - if keep { - newEntry[attr] = append(newEntry[attr], prevVal) - } else { - if strings.EqualFold(attr, "member") { - delMembers = append(delMembers, prevVal) - } - } - } - } - } else if change.Operation() == ldap.ModifyRequestChangeOperationReplace { - newEntry[attr] = []string{} - for _, newVal := range values { - newEntry[attr] = append(newEntry[attr], string(newVal)) - } - if strings.EqualFold(attr, "member") { - for _, newMem := range newEntry[attr] { - mustAdd := true - for _, prevMem := range prevEntry[attr] { - if prevMem == newMem { - mustAdd = false - break - } - } - if mustAdd { - addMembers = append(addMembers, newMem) - } - } - for _, prevMem := range prevEntry[attr] { - mustDel := true - for _, newMem := range newEntry[attr] { - if newMem == prevMem { - mustDel = false - break - } - } - if mustDel { - delMembers = append(delMembers, prevMem) - } - } - } - } - } - - // Check that added members actually exist - for _, addMem := range addMembers { - exists, err := server.objectExists(addMem) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - if !exists { - return ldap.LDAPResultNoSuchObject, fmt.Errorf( - "Cannot add member %s, it does not exist", addMem) - } - } - - newEntry[ATTR_MODIFIERSNAME] = []string{state.login.user} - newEntry[ATTR_MODIFYTIMESTAMP] = []string{genTimestamp()} - - // Save the edited values - server.addElements(dn, newEntry) - - // Update memberOf for added members and deleted members - for _, addMem := range addMembers { - memberOf, err := server.getAttribute(addMem, ATTR_MEMBEROF) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - if memberOf == nil { - memberOf = []string{} - } - memberOf = append(memberOf, dn) - err = server.addElements(addMem, Entry{ATTR_MEMBEROF: memberOf}) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - } - - for _, delMem := range delMembers { - memberOf, err := server.getAttribute(delMem, ATTR_MEMBEROF) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - if memberOf == nil { - memberOf = []string{} - } - newMemberOf := []string{} - for _, g := range memberOf { - if g != dn { - newMemberOf = append(newMemberOf, g) - } - } - - err = server.addElements(delMem, Entry{ATTR_MEMBEROF: newMemberOf}) - if err != nil { - return ldap.LDAPResultOperationsError, err - } - } - - return ldap.LDAPResultSuccess, nil -} diff --git a/read.go b/read.go new file mode 100644 index 0000000..04106c5 --- /dev/null +++ b/read.go @@ -0,0 +1,220 @@ +package main + +import ( + "fmt" + "strings" + + ldap "./ldapserver" + message "github.com/vjeantet/goldap/message" +) + + +// Compare request ------------------------- + +func (server *Server) handleCompare(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) { + state := s.(*State) + r := m.GetCompareRequest() + + code, err := server.handleCompareInternal(state, &r) + + res := ldap.NewResponse(code) + if err != nil { + res.SetDiagnosticMessage(err.Error()) + } + w.Write(message.CompareResponse(res)) +} + +func (server *Server) handleCompareInternal(state *State, r *message.CompareRequest) (int, error) { + dn := string(r.Entry()) + attr := string(r.Ava().AttributeDesc()) + expected := string(r.Ava().AssertionValue()) + + _, err := server.checkSuffix(dn, false) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + // Check permissions + if !server.config.Acl.Check(&state.login, dn, "read", []string{attr}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + + // Do query + exists, err := server.objectExists(dn) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if !exists { + return ldap.LDAPResultNoSuchObject, fmt.Errorf("Not found: %s", dn) + } + + values, err := server.getAttribute(dn, attr) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + + for _, v := range values { + if v == expected { + return ldap.LDAPResultCompareTrue, nil + } + } + + return ldap.LDAPResultCompareFalse, nil +} + + +// Search request ------------------------- + +func (server *Server) handleSearch(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) { + state := s.(*State) + r := m.GetSearchRequest() + + code, err := server.handleSearchInternal(state, w, &r) + + res := ldap.NewResponse(code) + if err != nil { + res.SetDiagnosticMessage(err.Error()) + } + w.Write(message.SearchResultDone(res)) +} + +func (server *Server) handleSearchInternal(state *State, w ldap.ResponseWriter, r *message.SearchRequest) (int, error) { + if DEBUG { + server.logger.Printf("-- SEARCH REQUEST: --") + server.logger.Printf("Request BaseDn=%s", r.BaseObject()) + server.logger.Printf("Request Filter=%s", r.Filter()) + server.logger.Printf("Request FilterString=%s", r.FilterString()) + server.logger.Printf("Request Attributes=%s", r.Attributes()) + server.logger.Printf("Request TimeLimit=%d", r.TimeLimit().Int()) + } + + if !server.config.Acl.Check(&state.login, "read", string(r.BaseObject()), []string{}) { + return ldap.LDAPResultInsufficientAccessRights, fmt.Errorf("Please specify a base object on which you have read rights") + } + + baseObject, err := server.checkSuffix(string(r.BaseObject()), true) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + basePath, err := dnToConsul(baseObject) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + data, _, err := server.kv.List(basePath+"/", nil) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + + entries, err := parseConsulResult(data) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if DEBUG { + server.logger.Printf("in %s: %#v", basePath+"/", data) + server.logger.Printf("%#v", entries) + } + + for dn, entry := range entries { + // Filter out if we don't match requested filter + matched, err := applyFilter(entry, r.Filter()) + if err != nil { + return ldap.LDAPResultUnwillingToPerform, err + } + if !matched { + continue + } + + // Filter out if user is not allowed to read this + if !server.config.Acl.Check(&state.login, "read", dn, []string{}) { + continue + } + + e := ldap.NewSearchResultEntry(dn) + for attr, val := range entry { + // If attribute is not in request, exclude it from returned entry + if len(r.Attributes()) > 0 { + found := false + for _, requested := range r.Attributes() { + if strings.EqualFold(string(requested), attr) { + found = true + break + } + } + if !found { + continue + } + } + // If we are not allowed to read attribute, exclude it from returned entry + if !server.config.Acl.Check(&state.login, "read", dn, []string{attr}) { + continue + } + // Send result + for _, v := range val { + e.AddAttribute(message.AttributeDescription(attr), + message.AttributeValue(v)) + } + } + w.Write(e) + } + + return ldap.LDAPResultSuccess, nil +} + +func applyFilter(entry Entry, filter message.Filter) (bool, error) { + if fAnd, ok := filter.(message.FilterAnd); ok { + for _, cond := range fAnd { + res, err := applyFilter(entry, cond) + if err != nil { + return false, err + } + if !res { + return false, nil + } + } + return true, nil + } else if fOr, ok := filter.(message.FilterOr); ok { + for _, cond := range fOr { + res, err := applyFilter(entry, cond) + if err != nil { + return false, err + } + if res { + return true, nil + } + } + return false, nil + } else if fNot, ok := filter.(message.FilterNot); ok { + res, err := applyFilter(entry, fNot.Filter) + if err != nil { + return false, err + } + return !res, nil + } else if fPresent, ok := filter.(message.FilterPresent); ok { + what := string(fPresent) + // Case insensitive search + for desc, values := range entry { + if strings.EqualFold(what, desc) { + return len(values) > 0, nil + } + } + return false, nil + } else if fEquality, ok := filter.(message.FilterEqualityMatch); ok { + desc := string(fEquality.AttributeDesc()) + target := string(fEquality.AssertionValue()) + // Case insensitive attribute search + for entry_desc, value := range entry { + if strings.EqualFold(entry_desc, desc) { + for _, val := range value { + if val == target { + return true, nil + } + } + return false, nil + } + } + return false, nil + } else { + return false, fmt.Errorf("Unsupported filter: %#v %T", filter, filter) + } +} diff --git a/write.go b/write.go new file mode 100644 index 0000000..33779c4 --- /dev/null +++ b/write.go @@ -0,0 +1,472 @@ +package main + +import ( + "fmt" + "strings" + + ldap "./ldapserver" + message "github.com/vjeantet/goldap/message" +) + + +// Add request ------------------------ + +func (server *Server) handleAdd(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) { + state := s.(*State) + r := m.GetAddRequest() + + code, err := server.handleAddInternal(state, &r) + + res := ldap.NewResponse(code) + if err != nil { + res.SetDiagnosticMessage(err.Error()) + } + if code == ldap.LDAPResultSuccess { + server.logger.Printf("Successfully added %s", string(r.Entry())) + } else { + server.logger.Printf("Failed to add %s (%s)", string(r.Entry()), err) + } + w.Write(message.AddResponse(res)) +} + +func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (int, error) { + dn := string(r.Entry()) + + _, err := server.checkSuffix(dn, false) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + dnSplit, err := parseDN(dn) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + // Check permissions + attrListStr := []string{} + for _, attribute := range r.Attributes() { + attrListStr = append(attrListStr, string(attribute.Type_())) + } + if !server.config.Acl.Check(&state.login, "add", dn, attrListStr) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + + // Check that object does not already exist + exists, err := server.objectExists(dn) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if exists { + return ldap.LDAPResultEntryAlreadyExists, nil + } + + // Add object + + // If adding a group, track of who the members will be so that their memberOf field can be updated later + var members []string = nil + + entry := Entry{} + for _, attribute := range r.Attributes() { + key := string(attribute.Type_()) + vals_str := []string{} + for _, val := range attribute.Vals() { + vals_str = append(vals_str, string(val)) + } + + // Fail if they are trying to write memberOf, we manage this ourselves + err = checkRestrictedAttr(key) + if err != nil { + return ldap.LDAPResultObjectClassViolation, err + } + // If they are writing a member key, we have to check they are adding valid members + if strings.EqualFold(key, "member") { + members = vals_str + for _, member := range members { + _, err := server.checkSuffix(member, false) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + exists, err = server.objectExists(member) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if !exists { + return ldap.LDAPResultNoSuchObject, fmt.Errorf( + "Cannot add %s to members, it does not exist!", + member) + } + } + } + entry[key] = vals_str + } + + entry[ATTR_CREATORSNAME] = []string{state.login.user} + entry[ATTR_CREATETIMESTAMP] = []string{genTimestamp()} + entry[ATTR_ENTRYUUID] = []string{genUuid()} + entry[dnSplit[0].Type] = []string{dnSplit[0].Value} + + err = server.addElements(dn, entry) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + + if members != nil { + for _, member := range members { + memberGroups, err := server.getAttribute(member, ATTR_MEMBEROF) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if memberGroups == nil { + memberGroups = []string{} + } + + alreadyMember := false + for _, mb := range memberGroups { + if mb == dn { + alreadyMember = true + server.logger.Printf("Warning: inconsistency detected, %s was memberOf %s at a time when it didn't exist!", + member, dn) + break + } + } + + if !alreadyMember { + memberGroups = append(memberGroups, dn) + err = server.addElements(member, Entry{ + ATTR_MEMBEROF: memberGroups, + }) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + } + } + } + + return ldap.LDAPResultSuccess, nil +} + + +// Delete request ------------------------ + +func (server *Server) handleDelete(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) { + state := s.(*State) + r := m.GetDeleteRequest() + + code, err := server.handleDeleteInternal(state, &r) + + res := ldap.NewResponse(code) + if err != nil { + res.SetDiagnosticMessage(err.Error()) + } + if code == ldap.LDAPResultSuccess { + server.logger.Printf("Successfully deleted %s", string(r)) + } else { + server.logger.Printf("Failed to delete %s (%s)", string(r), err) + } + w.Write(message.DelResponse(res)) +} + +func (server *Server) handleDeleteInternal(state *State, r *message.DelRequest) (int, error) { + dn := string(*r) + + _, err := server.checkSuffix(dn, false) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + // Check for delete permission + if !server.config.Acl.Check(&state.login, "delete", dn, []string{}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + + // Check that this LDAP entry exists and has no children + path, err := dnToConsul(dn) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + items, _, err := server.kv.List(path+"/", nil) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + + if len(items) == 0 { + return ldap.LDAPResultNoSuchObject, fmt.Errorf("Not found: %s", dn) + } + for _, item := range items { + itemDN, _, err := consulToDN(item.Key) + if err != nil { + continue + } + if itemDN != dn { + return ldap.LDAPResultNotAllowedOnNonLeaf, fmt.Errorf( + "Cannot delete %d as it has children", dn) + } + } + + // Retrieve group membership before we delete everything + memberOf, err := server.getAttribute(dn, ATTR_MEMBEROF) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + + // Delete the LDAP entry + _, err = server.kv.DeleteTree(path+"/", nil) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + + // Delete it from the member list of all the groups it was a member of + if memberOf != nil { + for _, group := range memberOf { + groupMembers, err := server.getAttribute(dn, "member") + if err != nil { + return ldap.LDAPResultOperationsError, err + } + + newMembers := []string{} + for _, memb := range groupMembers { + if memb != dn { + newMembers = append(newMembers, memb) + } + } + + err = server.addElements(group, Entry{ + "member": newMembers, + }) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + } + } + + return ldap.LDAPResultSuccess, nil +} + + +// Modify request ------------------------ + +func (server *Server) handleModify(s ldap.UserState, w ldap.ResponseWriter, m *ldap.Message) { + state := s.(*State) + r := m.GetModifyRequest() + + code, err := server.handleModifyInternal(state, &r) + + res := ldap.NewResponse(code) + if err != nil { + res.SetDiagnosticMessage(err.Error()) + } + if code == ldap.LDAPResultSuccess { + server.logger.Printf("Successfully modified %s", string(r.Object())) + } else { + server.logger.Printf("Failed to modifiy %s (%s)", string(r.Object()), err) + } + w.Write(message.ModifyResponse(res)) +} + +func (server *Server) handleModifyInternal(state *State, r *message.ModifyRequest) (int, error) { + dn := string(r.Object()) + + _, err := server.checkSuffix(dn, false) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + dnSplit, err := parseDN(dn) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + // First permission check with no particular attributes + if !server.config.Acl.Check(&state.login, "modify", dn, []string{}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + + // Retrieve previous values (by the way, check object exists) + path, err := dnToConsul(dn) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + items, _, err := server.kv.List(path+"/attribute=", nil) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + + if len(items) == 0 { + return ldap.LDAPResultNoSuchObject, fmt.Errorf("Not found: %s", dn) + } + + prevEntry := Entry{} + for _, item := range items { + itemDN, attr, err := consulToDN(item.Key) + if err != nil { + continue + } + if itemDN != dn { + panic("itemDN != dn in handleModifyInternal") + } + vals, err := parseValue(item.Value) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + prevEntry[attr] = vals + } + + // Keep track of group members added/deleted + addMembers, delMembers := []string{}, []string{} + + // Produce new entry values to be saved + newEntry := Entry{} + for _, change := range r.Changes() { + attr := string(change.Modification().Type_()) + values := change.Modification().Vals() + + err = checkRestrictedAttr(attr) + if err != nil { + return ldap.LDAPResultObjectClassViolation, err + } + if strings.EqualFold(attr, dnSplit[0].Type) { + return ldap.LDAPResultObjectClassViolation, fmt.Errorf("%s may not be changed as it is part of object path", attr) + } + + // Check for permission to modify this attribute + if !server.config.Acl.Check(&state.login, "modify", dn, []string{attr}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + + if change.Operation() == ldap.ModifyRequestChangeOperationAdd { + newEntry[attr] = prevEntry[attr] + for _, val := range values { + present := false + for _, prevVal := range newEntry[attr] { + if prevVal == string(val) { + present = true + break + } + } + if !present { + newEntry[attr] = append(newEntry[attr], string(val)) + if strings.EqualFold(attr, "member") { + addMembers = append(addMembers, string(val)) + } + } + } + } else if change.Operation() == ldap.ModifyRequestChangeOperationDelete { + if len(values) == 0 { + // Delete everything + newEntry[attr] = []string{} + if strings.EqualFold(attr, "member") { + delMembers = append(delMembers, prevEntry[attr]...) + } + } else { + // Delete only those specified + newEntry[attr] = []string{} + for _, prevVal := range prevEntry[attr] { + keep := true + for _, delVal := range values { + if string(delVal) == prevVal { + keep = false + break + } + } + if keep { + newEntry[attr] = append(newEntry[attr], prevVal) + } else { + if strings.EqualFold(attr, "member") { + delMembers = append(delMembers, prevVal) + } + } + } + } + } else if change.Operation() == ldap.ModifyRequestChangeOperationReplace { + newEntry[attr] = []string{} + for _, newVal := range values { + newEntry[attr] = append(newEntry[attr], string(newVal)) + } + if strings.EqualFold(attr, "member") { + for _, newMem := range newEntry[attr] { + mustAdd := true + for _, prevMem := range prevEntry[attr] { + if prevMem == newMem { + mustAdd = false + break + } + } + if mustAdd { + addMembers = append(addMembers, newMem) + } + } + for _, prevMem := range prevEntry[attr] { + mustDel := true + for _, newMem := range newEntry[attr] { + if newMem == prevMem { + mustDel = false + break + } + } + if mustDel { + delMembers = append(delMembers, prevMem) + } + } + } + } + } + + // Check that added members actually exist + for _, addMem := range addMembers { + exists, err := server.objectExists(addMem) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if !exists { + return ldap.LDAPResultNoSuchObject, fmt.Errorf( + "Cannot add member %s, it does not exist", addMem) + } + } + + newEntry[ATTR_MODIFIERSNAME] = []string{state.login.user} + newEntry[ATTR_MODIFYTIMESTAMP] = []string{genTimestamp()} + + // Save the edited values + server.addElements(dn, newEntry) + + // Update memberOf for added members and deleted members + for _, addMem := range addMembers { + memberOf, err := server.getAttribute(addMem, ATTR_MEMBEROF) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if memberOf == nil { + memberOf = []string{} + } + memberOf = append(memberOf, dn) + err = server.addElements(addMem, Entry{ATTR_MEMBEROF: memberOf}) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + } + + for _, delMem := range delMembers { + memberOf, err := server.getAttribute(delMem, ATTR_MEMBEROF) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if memberOf == nil { + memberOf = []string{} + } + newMemberOf := []string{} + for _, g := range memberOf { + if g != dn { + newMemberOf = append(newMemberOf, g) + } + } + + err = server.addElements(delMem, Entry{ATTR_MEMBEROF: newMemberOf}) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + } + + return ldap.LDAPResultSuccess, nil +}