summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--filter.go11
-rw-r--r--server.go16
-rw-r--r--server_search.go47
-rw-r--r--server_search_test.go36
-rw-r--r--server_test.go49
6 files changed, 137 insertions, 23 deletions
diff --git a/.gitignore b/.gitignore
index 87275bf..1cc73fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ examples/modify
examples/search
examples/searchSSL
examples/searchTLS
+.idea
diff --git a/filter.go b/filter.go
index 9f4c949..05c4bb2 100644
--- a/filter.go
+++ b/filter.go
@@ -315,22 +315,23 @@ func ServerApplyFilter(f *ber.Packet, entry *Entry) (bool, LDAPResultCode) {
return false, LDAPResultOperationsError
}
attribute := f.Children[0].Value.(string)
- bytes := f.Children[1].Children[0].Data.Bytes()
- value := string(bytes[:])
+ valueBytes := f.Children[1].Children[0].Data.Bytes()
+ valueLower := strings.ToLower(string(valueBytes[:]))
for _, a := range entry.Attributes {
if strings.ToLower(a.Name) == strings.ToLower(attribute) {
for _, v := range a.Values {
+ vLower := strings.ToLower(v)
switch f.Children[1].Children[0].Tag {
case FilterSubstringsInitial:
- if strings.HasPrefix(v, value) {
+ if strings.HasPrefix(vLower, valueLower) {
return true, LDAPResultSuccess
}
case FilterSubstringsAny:
- if strings.Contains(v, value) {
+ if strings.Contains(vLower, valueLower) {
return true, LDAPResultSuccess
}
case FilterSubstringsFinal:
- if strings.HasSuffix(v, value) {
+ if strings.HasSuffix(vLower, valueLower) {
return true, LDAPResultSuccess
}
}
diff --git a/server.go b/server.go
index 3576475..24b1e77 100644
--- a/server.go
+++ b/server.go
@@ -376,14 +376,20 @@ func sendPacket(conn net.Conn, packet *ber.Packet) error {
//
func routeFunc(dn string, funcNames []string) string {
bestPick := ""
+ bestPickWeight := 0
+ dnMatch := "," + strings.ToLower(dn)
+ var weight int
for _, fn := range funcNames {
- if strings.HasSuffix(dn, fn) {
- l := len(strings.Split(bestPick, ","))
- if bestPick == "" {
- l = 0
+ if strings.HasSuffix(dnMatch, "," + fn) {
+ // empty string as 0, no-comma string 1 , etc
+ if fn == "" {
+ weight = 0
+ } else {
+ weight = strings.Count(fn, ",") + 1
}
- if len(strings.Split(fn, ",")) > l {
+ if weight > bestPickWeight {
bestPick = fn
+ bestPickWeight = weight
}
}
}
diff --git a/server_search.go b/server_search.go
index b4f7a5f..12a6caf 100644
--- a/server_search.go
+++ b/server_search.go
@@ -46,6 +46,7 @@ func HandleSearchRequest(req *ber.Packet, controls *[]Control, messageID uint64,
}
i := 0
+ searchReqBaseDNLower := strings.ToLower(searchReq.BaseDN)
for _, entry := range searchResp.Entries {
if server.EnforceLDAP {
// filter
@@ -61,25 +62,24 @@ func HandleSearchRequest(req *ber.Packet, controls *[]Control, messageID uint64,
switch searchReq.Scope {
case ScopeWholeSubtree: // The scope is constrained to the entry named by baseObject and to all its subordinates.
case ScopeBaseObject: // The scope is constrained to the entry named by baseObject.
- if entry.DN != searchReq.BaseDN {
+ if strings.ToLower(entry.DN) != searchReqBaseDNLower {
continue
}
case ScopeSingleLevel: // The scope is constrained to the immediate subordinates of the entry named by baseObject.
- parts := strings.Split(entry.DN, ",")
- if len(parts) < 2 && entry.DN != searchReq.BaseDN {
+ entryDNLower := strings.ToLower(entry.DN)
+ parts := strings.Split(entryDNLower, ",")
+ if len(parts) < 2 && entryDNLower != searchReqBaseDNLower {
continue
}
- if dn := strings.Join(parts[1:], ","); dn != searchReq.BaseDN {
+ if dnSuffix := strings.Join(parts[1:], ","); dnSuffix != searchReqBaseDNLower {
continue
}
}
- // attributes
- if len(searchReq.Attributes) > 1 || (len(searchReq.Attributes) == 1 && len(searchReq.Attributes[0]) > 0) {
- entry, err = filterAttributes(entry, searchReq.Attributes)
- if err != nil {
- return NewError(LDAPResultOperationsError, err)
- }
+ // filter attributes
+ entry, err = filterAttributes(entry, searchReq.Attributes)
+ if err != nil {
+ return NewError(LDAPResultOperationsError, err)
}
// size limit
@@ -160,9 +160,30 @@ func filterAttributes(entry *Entry, attributes []string) (*Entry, error) {
// only return requested attributes
newAttributes := []*EntryAttribute{}
- for _, attr := range entry.Attributes {
- for _, requested := range attributes {
- if requested == "*" || strings.ToLower(attr.Name) == strings.ToLower(requested) {
+ if len(attributes) > 1 || (len(attributes) == 1 && len(attributes[0]) > 0) {
+ for _, attr := range entry.Attributes {
+ attrNameLower := strings.ToLower(attr.Name)
+ for _, requested := range attributes {
+ requestedLower := strings.ToLower(requested)
+ // You can request the directory server to return operational attributes by adding + (the plus sign) in your ldapsearch command.
+ // "+supportedControl" is treated as an operational attribute
+ if strings.HasPrefix(attrNameLower, "+") {
+ if requestedLower == "+" || attrNameLower == "+"+requestedLower {
+ newAttributes = append(newAttributes, &EntryAttribute{attr.Name[1:], attr.Values})
+ break
+ }
+ } else {
+ if requested == "*" || attrNameLower == requestedLower {
+ newAttributes = append(newAttributes, attr)
+ break
+ }
+ }
+ }
+ }
+ } else {
+ // remove operational attributes
+ for _, attr := range entry.Attributes {
+ if !strings.HasPrefix(attr.Name, "+") {
newAttributes = append(newAttributes, attr)
}
}
diff --git a/server_search_test.go b/server_search_test.go
index 5a083b0..09e2b14 100644
--- a/server_search_test.go
+++ b/server_search_test.go
@@ -451,6 +451,42 @@ func TestSearchScope(t *testing.T) {
quit <- true
}
+
+/////////////////////////
+func TestSearchScopeCaseInsensitive(t *testing.T) {
+ quit := make(chan bool)
+ done := make(chan bool)
+ go func() {
+ s := NewServer()
+ s.EnforceLDAP = true
+ s.QuitChannel(quit)
+ s.SearchFunc("", searchCaseInsensitive{})
+ s.BindFunc("", bindCaseInsensitive{})
+ if err := s.ListenAndServe(listenString); err != nil {
+ t.Errorf("s.ListenAndServe failed: %s", err.Error())
+ }
+ }()
+
+ go func() {
+ cmd := exec.Command("ldapsearch", "-H", ldapURL, "-x",
+ "-b", "cn=Case,o=testers,c=test", "-D", "cn=CAse,o=testers,c=test", "-w", "iLike2test", "-s", "base", "cn=CASe")
+ out, _ := cmd.CombinedOutput()
+ if !strings.Contains(string(out), "dn: cn=CASE,o=testers,c=test") {
+ t.Errorf("ldapsearch 'base' scope failed - didn't find expected DN: %v", string(out))
+ }
+
+ done <- true
+ }()
+
+ select {
+ case <-done:
+ case <-time.After(timeout):
+ t.Errorf("ldapsearch command timed out")
+ }
+ quit <- true
+}
+
+
func TestSearchControls(t *testing.T) {
quit := make(chan bool)
done := make(chan bool)
diff --git a/server_test.go b/server_test.go
index 88c47bf..233f7ea 100644
--- a/server_test.go
+++ b/server_test.go
@@ -329,6 +329,17 @@ func (b bindPanic) Bind(bindDN, bindSimplePw string, conn net.Conn) (LDAPResultC
return LDAPResultInvalidCredentials, nil
}
+type bindCaseInsensitive struct {
+}
+
+func (b bindCaseInsensitive) Bind(bindDN, bindSimplePw string, conn net.Conn) (LDAPResultCode, error) {
+ if strings.ToLower(bindDN) == "cn=case,o=testers,c=test" && bindSimplePw == "iLike2test" {
+ return LDAPResultSuccess, nil
+ }
+ return LDAPResultInvalidCredentials, nil
+}
+
+
type searchSimple struct {
}
@@ -408,3 +419,41 @@ func (s searchControls) Search(boundDN string, searchReq SearchRequest, conn net
}
return ServerSearchResult{entries, []string{}, []Control{}, LDAPResultSuccess}, nil
}
+
+
+type searchCaseInsensitive struct {
+}
+
+func (s searchCaseInsensitive) Search(boundDN string, searchReq SearchRequest, conn net.Conn) (ServerSearchResult, error) {
+ entries := []*Entry{
+ &Entry{"cn=CASE,o=testers,c=test", []*EntryAttribute{
+ &EntryAttribute{"cn", []string{"CaSe"}},
+ &EntryAttribute{"o", []string{"ate"}},
+ &EntryAttribute{"uidNumber", []string{"5005"}},
+ &EntryAttribute{"accountstatus", []string{"active"}},
+ &EntryAttribute{"uid", []string{"trent"}},
+ &EntryAttribute{"description", []string{"trent via sa"}},
+ &EntryAttribute{"objectclass", []string{"posixaccount"}},
+ }},
+ }
+ return ServerSearchResult{entries, []string{}, []Control{}, LDAPResultSuccess}, nil
+}
+
+
+func TestRouteFunc(t *testing.T) {
+ if routeFunc("", []string{"a", "xyz", "tt"}) != "" {
+ t.Error("routeFunc failed")
+ }
+ if routeFunc("a=b", []string{"a=b", "x=y,a=b", "tt"}) != "a=b" {
+ t.Error("routeFunc failed")
+ }
+ if routeFunc("x=y,a=b", []string{"a=b", "x=y,a=b", "tt"}) != "x=y,a=b" {
+ t.Error("routeFunc failed")
+ }
+ if routeFunc("x=y,a=b", []string{"x=y,a=b", "a=b", "tt"}) != "x=y,a=b" {
+ t.Error("routeFunc failed")
+ }
+ if routeFunc("nosuch", []string{"x=y,a=b", "a=b", "tt"}) != "" {
+ t.Error("routeFunc failed")
+ }
+}