|
|
@@ -5,9 +5,12 @@
|
|
|
package ldap
|
|
|
|
|
|
import (
|
|
|
+ "bytes"
|
|
|
+ hexpac "encoding/hex"
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
"strings"
|
|
|
+ "unicode/utf8"
|
|
|
|
|
|
"gopkg.in/asn1-ber.v1"
|
|
|
)
|
|
|
@@ -50,6 +53,20 @@ var FilterSubstringsMap = map[uint64]string{
|
|
|
FilterSubstringsFinal: "Substrings Final",
|
|
|
}
|
|
|
|
|
|
+const (
|
|
|
+ MatchingRuleAssertionMatchingRule = 1
|
|
|
+ MatchingRuleAssertionType = 2
|
|
|
+ MatchingRuleAssertionMatchValue = 3
|
|
|
+ MatchingRuleAssertionDNAttributes = 4
|
|
|
+)
|
|
|
+
|
|
|
+var MatchingRuleAssertionMap = map[uint64]string{
|
|
|
+ MatchingRuleAssertionMatchingRule: "Matching Rule Assertion Matching Rule",
|
|
|
+ MatchingRuleAssertionType: "Matching Rule Assertion Type",
|
|
|
+ MatchingRuleAssertionMatchValue: "Matching Rule Assertion Match Value",
|
|
|
+ MatchingRuleAssertionDNAttributes: "Matching Rule Assertion DN Attributes",
|
|
|
+}
|
|
|
+
|
|
|
func CompileFilter(filter string) (*ber.Packet, error) {
|
|
|
if len(filter) == 0 || filter[0] != '(' {
|
|
|
return nil, NewError(ErrorFilterCompile, errors.New("ldap: filter does not start with an '('"))
|
|
|
@@ -108,7 +125,7 @@ func DecompileFilter(packet *ber.Packet) (ret string, err error) {
|
|
|
if i == 0 && child.Tag != FilterSubstringsInitial {
|
|
|
ret += "*"
|
|
|
}
|
|
|
- ret += ber.DecodeString(child.Data.Bytes())
|
|
|
+ ret += EscapeFilter(ber.DecodeString(child.Data.Bytes()))
|
|
|
if child.Tag != FilterSubstringsFinal {
|
|
|
ret += "*"
|
|
|
}
|
|
|
@@ -116,22 +133,53 @@ func DecompileFilter(packet *ber.Packet) (ret string, err error) {
|
|
|
case FilterEqualityMatch:
|
|
|
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
|
|
ret += "="
|
|
|
- ret += ber.DecodeString(packet.Children[1].Data.Bytes())
|
|
|
+ ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
|
|
case FilterGreaterOrEqual:
|
|
|
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
|
|
ret += ">="
|
|
|
- ret += ber.DecodeString(packet.Children[1].Data.Bytes())
|
|
|
+ ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
|
|
case FilterLessOrEqual:
|
|
|
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
|
|
ret += "<="
|
|
|
- ret += ber.DecodeString(packet.Children[1].Data.Bytes())
|
|
|
+ ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
|
|
case FilterPresent:
|
|
|
ret += ber.DecodeString(packet.Data.Bytes())
|
|
|
ret += "=*"
|
|
|
case FilterApproxMatch:
|
|
|
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
|
|
ret += "~="
|
|
|
- ret += ber.DecodeString(packet.Children[1].Data.Bytes())
|
|
|
+ ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
|
|
+ case FilterExtensibleMatch:
|
|
|
+ attr := ""
|
|
|
+ dnAttributes := false
|
|
|
+ matchingRule := ""
|
|
|
+ value := ""
|
|
|
+
|
|
|
+ for _, child := range packet.Children {
|
|
|
+ switch child.Tag {
|
|
|
+ case MatchingRuleAssertionMatchingRule:
|
|
|
+ matchingRule = ber.DecodeString(child.Data.Bytes())
|
|
|
+ case MatchingRuleAssertionType:
|
|
|
+ attr = ber.DecodeString(child.Data.Bytes())
|
|
|
+ case MatchingRuleAssertionMatchValue:
|
|
|
+ value = ber.DecodeString(child.Data.Bytes())
|
|
|
+ case MatchingRuleAssertionDNAttributes:
|
|
|
+ dnAttributes = child.Value.(bool)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(attr) > 0 {
|
|
|
+ ret += attr
|
|
|
+ }
|
|
|
+ if dnAttributes {
|
|
|
+ ret += ":dn"
|
|
|
+ }
|
|
|
+ if len(matchingRule) > 0 {
|
|
|
+ ret += ":"
|
|
|
+ ret += matchingRule
|
|
|
+ }
|
|
|
+ ret += ":="
|
|
|
+ ret += EscapeFilter(value)
|
|
|
}
|
|
|
|
|
|
ret += ")"
|
|
|
@@ -155,58 +203,143 @@ func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) {
|
|
|
}
|
|
|
|
|
|
func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
|
|
- var packet *ber.Packet
|
|
|
- var err error
|
|
|
+ var (
|
|
|
+ packet *ber.Packet
|
|
|
+ err error
|
|
|
+ )
|
|
|
|
|
|
defer func() {
|
|
|
if r := recover(); r != nil {
|
|
|
err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter"))
|
|
|
}
|
|
|
}()
|
|
|
-
|
|
|
newPos := pos
|
|
|
- switch filter[pos] {
|
|
|
+
|
|
|
+ currentRune, currentWidth := utf8.DecodeRuneInString(filter[newPos:])
|
|
|
+
|
|
|
+ switch currentRune {
|
|
|
+ case utf8.RuneError:
|
|
|
+ return nil, 0, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos))
|
|
|
case '(':
|
|
|
- packet, newPos, err = compileFilter(filter, pos+1)
|
|
|
+ packet, newPos, err = compileFilter(filter, pos+currentWidth)
|
|
|
newPos++
|
|
|
return packet, newPos, err
|
|
|
case '&':
|
|
|
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[FilterAnd])
|
|
|
- newPos, err = compileFilterSet(filter, pos+1, packet)
|
|
|
+ newPos, err = compileFilterSet(filter, pos+currentWidth, packet)
|
|
|
return packet, newPos, err
|
|
|
case '|':
|
|
|
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[FilterOr])
|
|
|
- newPos, err = compileFilterSet(filter, pos+1, packet)
|
|
|
+ newPos, err = compileFilterSet(filter, pos+currentWidth, packet)
|
|
|
return packet, newPos, err
|
|
|
case '!':
|
|
|
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[FilterNot])
|
|
|
var child *ber.Packet
|
|
|
- child, newPos, err = compileFilter(filter, pos+1)
|
|
|
+ child, newPos, err = compileFilter(filter, pos+currentWidth)
|
|
|
packet.AppendChild(child)
|
|
|
return packet, newPos, err
|
|
|
default:
|
|
|
+ READING_ATTR := 0
|
|
|
+ READING_EXTENSIBLE_MATCHING_RULE := 1
|
|
|
+ READING_CONDITION := 2
|
|
|
+
|
|
|
+ state := READING_ATTR
|
|
|
+
|
|
|
attribute := ""
|
|
|
+ extensibleDNAttributes := false
|
|
|
+ extensibleMatchingRule := ""
|
|
|
condition := ""
|
|
|
- for newPos < len(filter) && filter[newPos] != ')' {
|
|
|
- switch {
|
|
|
- case packet != nil:
|
|
|
- condition += fmt.Sprintf("%c", filter[newPos])
|
|
|
- case filter[newPos] == '=':
|
|
|
- packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch])
|
|
|
- case filter[newPos] == '>' && filter[newPos+1] == '=':
|
|
|
- packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual])
|
|
|
- newPos++
|
|
|
- case filter[newPos] == '<' && filter[newPos+1] == '=':
|
|
|
- packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual])
|
|
|
- newPos++
|
|
|
- case filter[newPos] == '~' && filter[newPos+1] == '=':
|
|
|
- packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterLessOrEqual])
|
|
|
- newPos++
|
|
|
- case packet == nil:
|
|
|
- attribute += fmt.Sprintf("%c", filter[newPos])
|
|
|
- }
|
|
|
- newPos++
|
|
|
+
|
|
|
+ for newPos < len(filter) {
|
|
|
+ remainingFilter := filter[newPos:]
|
|
|
+ currentRune, currentWidth = utf8.DecodeRuneInString(remainingFilter)
|
|
|
+ if currentRune == ')' {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if currentRune == utf8.RuneError {
|
|
|
+ return packet, newPos, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos))
|
|
|
+ }
|
|
|
+
|
|
|
+ switch state {
|
|
|
+ case READING_ATTR:
|
|
|
+ switch {
|
|
|
+ // Extensible rule, with only DN-matching
|
|
|
+ case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:="):
|
|
|
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
|
|
|
+ extensibleDNAttributes = true
|
|
|
+ state = READING_CONDITION
|
|
|
+ newPos += 5
|
|
|
+
|
|
|
+ // Extensible rule, with DN-matching and a matching OID
|
|
|
+ case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:"):
|
|
|
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
|
|
|
+ extensibleDNAttributes = true
|
|
|
+ state = READING_EXTENSIBLE_MATCHING_RULE
|
|
|
+ newPos += 4
|
|
|
+
|
|
|
+ // Extensible rule, with attr only
|
|
|
+ case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="):
|
|
|
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
|
|
|
+ state = READING_CONDITION
|
|
|
+ newPos += 2
|
|
|
+
|
|
|
+ // Extensible rule, with no DN attribute matching
|
|
|
+ case currentRune == ':':
|
|
|
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
|
|
|
+ state = READING_EXTENSIBLE_MATCHING_RULE
|
|
|
+ newPos += 1
|
|
|
+
|
|
|
+ // Equality condition
|
|
|
+ case currentRune == '=':
|
|
|
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch])
|
|
|
+ state = READING_CONDITION
|
|
|
+ newPos += 1
|
|
|
+
|
|
|
+ // Greater-than or equal
|
|
|
+ case currentRune == '>' && strings.HasPrefix(remainingFilter, ">="):
|
|
|
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual])
|
|
|
+ state = READING_CONDITION
|
|
|
+ newPos += 2
|
|
|
+
|
|
|
+ // Less-than or equal
|
|
|
+ case currentRune == '<' && strings.HasPrefix(remainingFilter, "<="):
|
|
|
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual])
|
|
|
+ state = READING_CONDITION
|
|
|
+ newPos += 2
|
|
|
+
|
|
|
+ // Approx
|
|
|
+ case currentRune == '~' && strings.HasPrefix(remainingFilter, "~="):
|
|
|
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterApproxMatch])
|
|
|
+ state = READING_CONDITION
|
|
|
+ newPos += 2
|
|
|
+
|
|
|
+ // Still reading the attribute name
|
|
|
+ default:
|
|
|
+ attribute += fmt.Sprintf("%c", currentRune)
|
|
|
+ newPos += currentWidth
|
|
|
+ }
|
|
|
+
|
|
|
+ case READING_EXTENSIBLE_MATCHING_RULE:
|
|
|
+ switch {
|
|
|
+
|
|
|
+ // Matching rule OID is done
|
|
|
+ case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="):
|
|
|
+ state = READING_CONDITION
|
|
|
+ newPos += 2
|
|
|
+
|
|
|
+ // Still reading the matching rule oid
|
|
|
+ default:
|
|
|
+ extensibleMatchingRule += fmt.Sprintf("%c", currentRune)
|
|
|
+ newPos += currentWidth
|
|
|
+ }
|
|
|
+
|
|
|
+ case READING_CONDITION:
|
|
|
+ // append to the condition
|
|
|
+ condition += fmt.Sprintf("%c", currentRune)
|
|
|
+ newPos += currentWidth
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
if newPos == len(filter) {
|
|
|
err = NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
|
|
|
return packet, newPos, err
|
|
|
@@ -217,6 +350,36 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
|
|
}
|
|
|
|
|
|
switch {
|
|
|
+ case packet.Tag == FilterExtensibleMatch:
|
|
|
+ // MatchingRuleAssertion ::= SEQUENCE {
|
|
|
+ // matchingRule [1] MatchingRuleID OPTIONAL,
|
|
|
+ // type [2] AttributeDescription OPTIONAL,
|
|
|
+ // matchValue [3] AssertionValue,
|
|
|
+ // dnAttributes [4] BOOLEAN DEFAULT FALSE
|
|
|
+ // }
|
|
|
+
|
|
|
+ // Include the matching rule oid, if specified
|
|
|
+ if len(extensibleMatchingRule) > 0 {
|
|
|
+ packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule, MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule]))
|
|
|
+ }
|
|
|
+
|
|
|
+ // Include the attribute, if specified
|
|
|
+ if len(attribute) > 0 {
|
|
|
+ packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute, MatchingRuleAssertionMap[MatchingRuleAssertionType]))
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add the value (only required child)
|
|
|
+ encodedString, err := escapedStringToEncodedBytes(condition)
|
|
|
+ if err != nil {
|
|
|
+ return packet, newPos, err
|
|
|
+ }
|
|
|
+ packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchValue, encodedString, MatchingRuleAssertionMap[MatchingRuleAssertionMatchValue]))
|
|
|
+
|
|
|
+ // Defaults to false, so only include in the sequence if true
|
|
|
+ if extensibleDNAttributes {
|
|
|
+ packet.AppendChild(ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionDNAttributes, extensibleDNAttributes, MatchingRuleAssertionMap[MatchingRuleAssertionDNAttributes]))
|
|
|
+ }
|
|
|
+
|
|
|
case packet.Tag == FilterEqualityMatch && condition == "*":
|
|
|
packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute, FilterMap[FilterPresent])
|
|
|
case packet.Tag == FilterEqualityMatch && strings.Contains(condition, "*"):
|
|
|
@@ -238,15 +401,56 @@ func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
|
|
default:
|
|
|
tag = FilterSubstringsAny
|
|
|
}
|
|
|
- seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, part, FilterSubstringsMap[uint64(tag)]))
|
|
|
+ encodedString, err := escapedStringToEncodedBytes(part)
|
|
|
+ if err != nil {
|
|
|
+ return packet, newPos, err
|
|
|
+ }
|
|
|
+ seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, encodedString, FilterSubstringsMap[uint64(tag)]))
|
|
|
}
|
|
|
packet.AppendChild(seq)
|
|
|
default:
|
|
|
+ encodedString, err := escapedStringToEncodedBytes(condition)
|
|
|
+ if err != nil {
|
|
|
+ return packet, newPos, err
|
|
|
+ }
|
|
|
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
|
|
|
- packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, condition, "Condition"))
|
|
|
+ packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, encodedString, "Condition"))
|
|
|
}
|
|
|
|
|
|
- newPos++
|
|
|
+ newPos += currentWidth
|
|
|
return packet, newPos, err
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+// Convert from "ABC\xx\xx\xx" form to literal bytes for transport
|
|
|
+func escapedStringToEncodedBytes(escapedString string) (string, error) {
|
|
|
+ var buffer bytes.Buffer
|
|
|
+ i := 0
|
|
|
+ for i < len(escapedString) {
|
|
|
+ currentRune, currentWidth := utf8.DecodeRuneInString(escapedString[i:])
|
|
|
+ if currentRune == utf8.RuneError {
|
|
|
+ return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", i))
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check for escaped hex characters and convert them to their literal value for transport.
|
|
|
+ if currentRune == '\\' {
|
|
|
+ // http://tools.ietf.org/search/rfc4515
|
|
|
+ // \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not
|
|
|
+ // being a member of UTF1SUBSET.
|
|
|
+ if i+2 > len(escapedString) {
|
|
|
+ return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter"))
|
|
|
+ }
|
|
|
+ if escByte, decodeErr := hexpac.DecodeString(escapedString[i+1 : i+3]); decodeErr != nil {
|
|
|
+ return "", NewError(ErrorFilterCompile, errors.New("ldap: invalid characters for escape in filter"))
|
|
|
+ } else {
|
|
|
+ buffer.WriteByte(escByte[0])
|
|
|
+ i += 2 // +1 from end of loop, so 3 total for \xx.
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ buffer.WriteRune(currentRune)
|
|
|
+ }
|
|
|
+
|
|
|
+ i += currentWidth
|
|
|
+ }
|
|
|
+ return buffer.String(), nil
|
|
|
+}
|