From 825aa770893850fb2626def316c4b0f060ef776f Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Sat, 15 Feb 2020 12:04:06 +0100 Subject: Hopefully, fix most case-sensitivity issues - DNs are always used in canonical form: lowercase, no spaces. This is how they are internally handled and stored in paths and fields such as member and memberof - Attribute names now can have any combination of lower/uppercase and stuff should work - When modifying an attribute with a name that hase a different lower/upper combination than the previously stored value, keep the previous attribute name - Trim spaces from values and do not store empty values --- write.go | 166 ++++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 95 insertions(+), 71 deletions(-) (limited to 'write.go') diff --git a/write.go b/write.go index 7a71465..b04cd2c 100644 --- a/write.go +++ b/write.go @@ -58,7 +58,16 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in return ldap.LDAPResultEntryAlreadyExists, nil } - // TODO: check that parent object exists + // Check that parent object exists + parentDn := unparseDN(dnSplit[1:]) + parentExists, err := server.objectExists(parentDn) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if !parentExists { + return ldap.LDAPResultNoSuchObject, fmt.Errorf( + "Parent object %s does not exist", parentDn) + } // If adding a group, track of who the members will be so that their memberOf field can be updated later members := []string{} @@ -77,8 +86,9 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in 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) { + // If they are writing a member list, we have to check they are adding valid members + // Also, rewrite member list to use canonical DN syntax (no spaces, all lowercase) for _, member := range vals_str { member_canonical, err := server.checkDN(member, false) if err != nil { @@ -93,19 +103,31 @@ func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (in "Cannot add %s to members, it does not exist!", member_canonical) } + members = append(members, member_canonical) } - members = append(members, vals_str...) - } - if prev, ok := entry[key]; ok { - entry[key] = append(prev, vals_str...) + entry[key] = members } else { - entry[key] = vals_str + if prev, ok := entry[key]; ok { + entry[key] = append(prev, vals_str...) + } else { + entry[key] = vals_str + } } } - if _, ok := entry[ATTR_OBJECTCLASS]; !ok { + // Ensure object has at least one objectclass value + hasObjectClass := false + for k := range entry { + if strings.EqualFold(k, ATTR_OBJECTCLASS) { + hasObjectClass = true + break + } + } + if !hasObjectClass { entry[ATTR_OBJECTCLASS] = []string{"top"} } + + // Write system attributes entry[ATTR_CREATORSNAME] = []string{state.login.user} entry[ATTR_CREATETIMESTAMP] = []string{genTimestamp()} entry[ATTR_ENTRYUUID] = []string{genUuid()} @@ -306,11 +328,25 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques addMembers, delMembers := []string{}, []string{} // Produce new entry values to be saved - newEntry := Entry{} + entry := Entry{} + for _, change := range r.Changes() { attr := string(change.Modification().Type_()) - values := change.Modification().Vals() + changeValues := []string{} + for _, v := range change.Modification().Vals() { + changeValues = append(changeValues, string(v)) + } + // If we already had an attribute with this name before, + // make sure we are using the same lowercase/uppercase + for prevAttr := range prevEntry { + if strings.EqualFold(attr, prevAttr) { + attr = prevAttr + break + } + } + + // Check that this attribute is not system-managed thus restricted err = checkRestrictedAttr(attr) if err != nil { return ldap.LDAPResultObjectClassViolation, err @@ -326,112 +362,100 @@ func (server *Server) handleModifyInternal(state *State, r *message.ModifyReques 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 we are changing ATTR_MEMBER, rewrite all values to canonical form + if strings.EqualFold(attr, ATTR_MEMBER) { + for i := range changeValues { + canonical_val, err := server.checkDN(changeValues[i], false) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err } - if !present { - newEntry[attr] = append(newEntry[attr], string(val)) + changeValues[i] = canonical_val + } + } + + // If we don't yet have a new value for this attr, + // but one existed before, initialize entry[attr] to the old value + // so that later on what we do is simply modify entry[attr] in place + // (this allows to handle sequences of several changes on the same attr) + if _, ok := entry[attr]; !ok { + if _, ok := prevEntry[attr]; ok { + entry[attr] = prevEntry[attr] + } + } + + // Apply effective modification on entry[attr] + if change.Operation() == ldap.ModifyRequestChangeOperationAdd { + for _, val := range changeValues { + if !listContains(entry[attr], val) { + entry[attr] = append(entry[attr], val) if strings.EqualFold(attr, ATTR_MEMBER) { - addMembers = append(addMembers, string(val)) + addMembers = append(addMembers, val) } } } } else if change.Operation() == ldap.ModifyRequestChangeOperationDelete { - if len(values) == 0 { + if len(changeValues) == 0 { // Delete everything - newEntry[attr] = []string{} if strings.EqualFold(attr, ATTR_MEMBER) { - delMembers = append(delMembers, prevEntry[attr]...) + delMembers = append(delMembers, entry[attr]...) } + entry[attr] = []string{} } 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) + newList := []string{} + for _, prevVal := range entry[attr] { + if !listContains(changeValues, prevVal) { + newList = append(newList, prevVal) } else { if strings.EqualFold(attr, ATTR_MEMBER) { delMembers = append(delMembers, prevVal) } } } + entry[attr] = newList } } 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 { + for _, newMem := range changeValues { + if !listContains(entry[attr], newMem) { addMembers = append(addMembers, newMem) } } - for _, prevMem := range prevEntry[attr] { - mustDel := true - for _, newMem := range newEntry[attr] { - if newMem == prevMem { - mustDel = false - break - } - } - if mustDel { + for _, prevMem := range entry[attr] { + if !listContains(changeValues, prevMem) { delMembers = append(delMembers, prevMem) } } } + entry[attr] = changeValues } } // 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) + exists, err := server.objectExists(addMembers[i]) if err != nil { return ldap.LDAPResultOperationsError, err } if !exists { return ldap.LDAPResultNoSuchObject, fmt.Errorf( - "Cannot add member %s, it does not exist", addMem) + "Cannot add member %s, it does not exist", addMembers[i]) } - addMembers[i] = addMem } - if v, ok := newEntry[ATTR_OBJECTCLASS]; ok && len(v) == 0 { - return ldap.LDAPResultInsufficientAccessRights, fmt.Errorf( - "Cannot remove all objectclass values") + for k, v := range entry { + if strings.EqualFold(k, ATTR_OBJECTCLASS) && len(v) == 0 { + return ldap.LDAPResultInsufficientAccessRights, fmt.Errorf( + "Cannot remove all objectclass values") + } } // 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()} + entry[ATTR_MODIFIERSNAME] = []string{state.login.user} + entry[ATTR_MODIFYTIMESTAMP] = []string{genTimestamp()} // Save the edited values - err = server.addElements(dn, newEntry) + err = server.addElements(dn, entry) if err != nil { return ldap.LDAPResultOperationsError, err } -- cgit v1.2.3