package main import ( "fmt" "strings" ldap "bottin/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 } // If adding a group, track of who the members will be so that their memberOf field can be updated later var members []string = nil // Check attributes 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, ATTR_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} // Add our intem in the DB err = server.addElements(dn, entry) if err != nil { return ldap.LDAPResultOperationsError, err } // ~~ from this point on, our operation succeeded ~~ // ~~ future errors cause inconsistencies in the DB and are logged ~~ // If our item has a member list, add it to all of its member's memberOf attribute if members != nil { for _, member := range members { memberGroups, err := server.getAttribute(member, ATTR_MEMBEROF) if err != nil { server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, member, err) continue } 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 { server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, member, 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 } memberList, err := server.getAttribute(dn, ATTR_MEMBER) if err != nil { return ldap.LDAPResultOperationsError, err } // Delete the LDAP entry _, err = server.kv.DeleteTree(path+"/", nil) if err != nil { return ldap.LDAPResultOperationsError, err } // ~~ from this point on, our operation succeeded ~~ // ~~ future errors cause inconsistencies in the DB and are logged ~~ // 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(group, ATTR_MEMBER) if err != nil { server.logger.Printf("Could not remove %s from members of %s: %s", dn, group, err) continue } newMembers := []string{} for _, memb := range groupMembers { if memb != dn { newMembers = append(newMembers, memb) } } err = server.addElements(group, Entry{ ATTR_MEMBER: newMembers, }) if err != nil { server.logger.Printf("Could not remove %s from members of %s: %s", dn, group, err) } } } // Delete it from all of its member's memberOf info if memberList != nil { for _, member := range memberList { memberOf, err := server.getAttribute(member, ATTR_MEMBEROF) if err != nil || memberOf == nil { server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, member, err) continue } newMemberOf := []string{} for _, group := range memberOf { if group != dn { newMemberOf = append(newMemberOf, group) } } err = server.addElements(member, Entry{ ATTR_MEMBEROF: newMemberOf, }) if err != nil { server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, member, 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 { server.logger.Fatal("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, 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, 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, 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, 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) // ~~ from this point on, our operation succeeded ~~ // ~~ future errors cause inconsistencies in the DB and are logged ~~ // Update memberOf for added members and deleted members for _, addMem := range addMembers { memberOf, err := server.getAttribute(addMem, ATTR_MEMBEROF) if err != nil { server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, addMem, err) continue } if memberOf == nil { memberOf = []string{} } alreadyMember := false for _, mb := range memberOf { if mb == dn { alreadyMember = true break } } if !alreadyMember { memberOf = append(memberOf, dn) err = server.addElements(addMem, Entry{ ATTR_MEMBEROF: memberOf, }) if err != nil { server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, addMem, err) } } } for _, delMem := range delMembers { memberOf, err := server.getAttribute(delMem, ATTR_MEMBEROF) if err != nil { server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, delMem, err) continue } 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 { server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, delMem, err) } } return ldap.LDAPResultSuccess, nil }