package main import ( "fmt" "strings" ldap "bottin/ldapserver" message "github.com/lor00x/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, err := server.checkDN(string(r.Entry()), 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 } // TODO: check that parent object exists // If adding a group, track of who the members will be so that their memberOf field can be updated later members := []string{} // 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) { for _, member := range vals_str { member_canonical, err := server.checkDN(member, false) if err != nil { return ldap.LDAPResultInvalidDNSyntax, err } exists, err = server.objectExists(member_canonical) if err != nil { return ldap.LDAPResultOperationsError, err } if !exists { return ldap.LDAPResultNoSuchObject, fmt.Errorf( "Cannot add %s to members, it does not exist!", member_canonical) } } members = append(members, vals_str...) } if prev, ok := entry[key]; ok { entry[key] = append(prev, vals_str...) } else { 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 item 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 for _, member := range members { server.memberOfAdd(member, dn) } 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, err := server.checkDN(string(*r), 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 %s 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.Warnf("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.Warnf("Could not remove %s from members of %s: %s", dn, group, err) } } } // Delete it from all of its member's memberOf info for _, member := range memberList { server.memberOfRemove(member, dn) } 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, err := server.checkDN(string(r.Object()), 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 i := range addMembers { addMem, err := server.checkDN(addMembers[i], false) if err != nil { return ldap.LDAPResultInvalidDNSyntax, err } 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) } addMembers[i] = addMem } // Now, the modification has been processed and accepted and we want to commit it newEntry[ATTR_MODIFIERSNAME] = []string{state.login.user} newEntry[ATTR_MODIFYTIMESTAMP] = []string{genTimestamp()} // Save the edited values err = server.addElements(dn, newEntry) if err != nil { return ldap.LDAPResultOperationsError, err } // ~~ 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 { server.memberOfAdd(addMem, dn) } for _, delMem := range delMembers { server.memberOfRemove(delMem, dn) } return ldap.LDAPResultSuccess, nil }