package ldap import ( "cmp" "strings" "unicode" "unicode/utf8" ) type tri uint8 const ( triFalse tri = iota triTrue triUndefined ) type entry struct { dn string attrs map[string][]string } type matchKind uint8 const ( matchExact matchKind = iota matchASCIIFold matchUnicodeFold matchInteger ) type attrRule struct { name string match matchKind substrings bool ordering bool } var attrRules = indexRules([]attrRule{ {name: "uid", match: matchASCIIFold, substrings: true}, {name: "dc", match: matchASCIIFold}, {name: "ou", match: matchASCIIFold}, {name: "o", match: matchASCIIFold}, {name: "cn", match: matchUnicodeFold, substrings: true}, {name: "displayname", match: matchUnicodeFold, substrings: true}, {name: "sn", match: matchUnicodeFold}, {name: "description", match: matchUnicodeFold}, {name: "memberof", match: matchExact}, {name: "member", match: matchExact}, {name: "memberuid", match: matchExact}, {name: "sshpublickey", match: matchExact}, {name: "homedirectory", match: matchExact}, {name: "objectclass", match: matchASCIIFold}, {name: "entryuuid", match: matchASCIIFold}, {name: "uidnumber", match: matchInteger, ordering: true}, {name: "gidnumber", match: matchInteger, ordering: true}, }) func indexRules(rules []attrRule) map[string]*attrRule { m := make(map[string]*attrRule, len(rules)) for i := range rules { m[rules[i].name] = &rules[i] } return m } func lookupAttribute(desc string) *attrRule { return attrRules[asciiFold(desc)] } func (r *attrRule) normalize(s string) (string, bool) { switch r.match { case matchASCIIFold: return asciiFold(s), true case matchUnicodeFold: return unicodeFold(s) case matchInteger: return s, validInteger(s) } return s, true } func (f FilterExpr) eval(e *entry) tri { if f.root == nil || e == nil { return triUndefined } return f.root.eval(e) } func (n *filterNode) eval(e *entry) tri { switch n.kind { case filterAnd: result := triTrue for _, child := range n.children { switch child.eval(e) { case triFalse: return triFalse case triUndefined: result = triUndefined } } return result case filterOr: result := triFalse for _, child := range n.children { switch child.eval(e) { case triTrue: return triTrue case triUndefined: result = triUndefined } } return result case filterNot: switch n.children[0].eval(e) { case triTrue: return triFalse case triFalse: return triTrue } return triUndefined case filterEquality: return n.equality(e) case filterSubstrings: return n.substringMatch(e) case filterGreaterOrEqual: return n.ordering(e, func(c int) bool { return c >= 0 }) case filterLessOrEqual: return n.ordering(e, func(c int) bool { return c <= 0 }) case filterPresent: return n.present(e) } return triUndefined } func (n *filterNode) equality(e *entry) tri { if n.rule == nil { return triUndefined } want, ok := n.rule.normalize(string(n.value)) if !ok { return triUndefined } for _, v := range e.attrs[n.rule.name] { if got, ok := n.rule.normalize(v); ok && got == want { return triTrue } } return triFalse } func (n *filterNode) substringMatch(e *entry) tri { if n.rule == nil || !n.rule.substrings { return triUndefined } parts := make([]string, len(n.substrings)) for i, s := range n.substrings { part, ok := n.rule.normalize(string(s.value)) if !ok { return triUndefined } parts[i] = part } for _, v := range e.attrs[n.rule.name] { if got, ok := n.rule.normalize(v); ok && n.matchSubstrings(got, parts) { return triTrue } } return triFalse } func (n *filterNode) matchSubstrings(v string, parts []string) bool { for i, s := range n.substrings { part := parts[i] switch s.kind { case substringInitial: if !strings.HasPrefix(v, part) { return false } v = v[len(part):] case substringAny: j := strings.Index(v, part) if j < 0 { return false } v = v[j+len(part):] default: return strings.HasSuffix(v, part) } } return true } func (n *filterNode) ordering(e *entry, accept func(int) bool) tri { if n.rule == nil || !n.rule.ordering { return triUndefined } want := string(n.value) if !validInteger(want) { return triUndefined } for _, v := range e.attrs[n.rule.name] { if validInteger(v) && accept(compareIntegers(v, want)) { return triTrue } } return triFalse } func (n *filterNode) present(e *entry) tri { if n.rule == nil { return triUndefined } if len(e.attrs[n.rule.name]) > 0 { return triTrue } return triFalse } func asciiFold(s string) string { b := []byte(s) for i, ch := range b { if 'A' <= ch && ch <= 'Z' { b[i] = ch + ('a' - 'A') } } return string(b) } func unicodeFold(s string) (string, bool) { if !utf8.ValidString(s) { return "", false } var b strings.Builder b.Grow(len(s)) for _, r := range s { b.WriteRune(foldRune(r)) } return b.String(), true } func foldRune(r rune) rune { least := r for f := unicode.SimpleFold(r); f != r; f = unicode.SimpleFold(f) { least = min(least, f) } return least } func validInteger(s string) bool { digits := strings.TrimPrefix(s, "-") if digits == "" { return false } if digits[0] == '0' { return s == "0" } for i := range len(digits) { if !isDigit(digits[i]) { return false } } return true } func compareIntegers(a, b string) int { aNegative, bNegative := a[0] == '-', b[0] == '-' switch { case aNegative && !bNegative: return -1 case !aNegative && bNegative: return 1 case aNegative: return compareMagnitudes(b[1:], a[1:]) } return compareMagnitudes(a, b) } func compareMagnitudes(a, b string) int { if len(a) != len(b) { return cmp.Compare(len(a), len(b)) } return strings.Compare(a, b) }