package ldap import ( "context" "strconv" "strings" "github.com/ps78674/goldap/message" ldap "github.com/ps78674/ldapserver" "github.com/netauth/ldap/internal/buildinfo" pb "github.com/netauth/protocol" ) func (s *server) handleSearchDSE(w ldap.ResponseWriter, m *ldap.Message) { nc := strings.Join(s.nc, ",") r := m.GetSearchRequest() e := ldap.NewSearchResultEntry(nc) if len(r.Attributes()) == 0 { e.AddAttribute("vendorName", "NetAuth") e.AddAttribute("vendorVersion", message.AttributeValue(buildinfo.Version)) e.AddAttribute("objectClass", "top", "extensibleObject") e.AddAttribute("supportedLDAPVersion", "3") e.AddAttribute("namingContexts", message.AttributeValue(nc)) } w.Write(e) res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess) w.Write(res) } func (s *server) handleBaseDnSearch(w ldap.ResponseWriter, m *ldap.Message) { s.l.Debug("Base DN search") e := ldap.NewSearchResultEntry(strings.Join(s.nc, ",")) w.Write(e) res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess) w.Write(res) } func (s *server) handleSearchEntities(w ldap.ResponseWriter, m *ldap.Message) { ctx := context.Background() s.l.Debug("Search Entities") r := m.GetSearchRequest() expr, exprAnd, err := s.buildBleveQuery(r.Filter()) if err != nil { // If err is non-nil at this point it must mean that // the above match didn't find a supported filter. s.l.Warn("Unsupported Search Filter, this is a bug, please file a report", "filter", r.Filter()) res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultUnwillingToPerform) res.SetDiagnosticMessage("Filter type not supported") w.Write(res) return } s.l.Debug("Searching entities", "query", expr) var matchGroup string if exprAnd { s.l.Debug("Filtering by AND...") exprSlice := strings.Split(expr, " ") for i := range exprSlice { if !strings.Contains(exprSlice[i], "Name") && !strings.Contains(exprSlice[i], "ID") { matchGroup = exprSlice[i] if (i + 1) == len(exprSlice) { exprSlice = exprSlice[:i] } else { exprSlice = append(exprSlice[:i], exprSlice[i+1:]...) } break } } expr = strings.Join(exprSlice, " ") s.l.Debug("Filtered by AND", "expr", expr, "matchUid", matchGroup) } ents, err := s.c.EntitySearch(ctx, expr) if err != nil { res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultOperationsError) res.SetDiagnosticMessage(err.Error()) w.Write(res) return } for i := range ents { var e message.SearchResultEntry var complete bool var err error if exprAnd { e, complete, err = s.entitySearchResultWithMatch(ctx, ents[i], r.BaseObject(), r.Attributes(), matchGroup) if !complete { continue } } else { e, err = s.entitySearchResult(ctx, ents[i], r.BaseObject(), r.Attributes()) } if err != nil { res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultOperationsError) res.SetDiagnosticMessage(err.Error()) w.Write(res) return } w.Write(e) } s.l.Debug("Entities", "res", ents) res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess) w.Write(res) } // entitySearchResult maps an entity onto a SearchResultEntry, // performing the additional lookup for groups to populate the // memberOf attribute. Though not implemented, the attrs list is // plumbed down to this level to permit attribute filtering in the // future. func (s *server) entitySearchResult(ctx context.Context, e *pb.Entity, dn message.LDAPDN, attrs message.AttributeSelection) (message.SearchResultEntry, error) { s.l.Debug("Attr selection: ", "attrs", attrs, len(attrs)) res := ldap.NewSearchResultEntry("uid=" + e.GetID() + "," + string(dn)) res.AddAttribute("uid", message.AttributeValue(e.GetID())) res.AddAttribute("uidNumber", message.AttributeValue(strconv.Itoa(int(e.GetNumber())))) mail, err := s.c.EntityKVGet(ctx, e.GetID(), "mail") //if err != nil { // return res, err //} if len(mail["mail"]) > 0 { res.AddAttribute("mail", message.AttributeValue(mail["mail"][0])) } grps, err := s.c.EntityGroups(ctx, e.GetID()) if err != nil { return res, err } memberOf := []message.AttributeValue{} for i := range grps { g := "cn=" + grps[i].GetName() + ",ou=groups," + strings.Join(s.nc, ",") memberOf = append(memberOf, message.AttributeValue(g)) } res.AddAttribute("memberOf", memberOf...) entitySearchDN := "uid=" + e.GetID() + ",ou=entities," + strings.Join(s.nc, ",") s.routes.Search(s.handleSearchEntities). BaseDn(entitySearchDN). Scope(ldap.SearchRequestHomeSubtree). Label("Search - Entities (" + e.GetID() + ")") return res, nil } func (s *server) entitySearchResultWithMatch(ctx context.Context, e *pb.Entity, dn message.LDAPDN, attrs message.AttributeSelection, matchGroup string) (message.SearchResultEntry, bool, error) { s.l.Debug("Attr selection: ", "attrs", attrs, len(attrs)) res := ldap.NewSearchResultEntry("uid=" + e.GetID() + "," + string(dn)) res.AddAttribute("uid", message.AttributeValue(e.GetID())) res.AddAttribute("uidNumber", message.AttributeValue(strconv.Itoa(int(e.GetNumber())))) res.AddAttribute("displayName", message.AttributeValue(e.GetMeta().GetDisplayName())) mail, err := s.c.EntityKVGet(ctx, e.GetID(), "mail") if len(mail["mail"]) > 0 { res.AddAttribute("mail", message.AttributeValue(mail["mail"][0])) } grps, err := s.c.EntityGroups(ctx, e.GetID()) if err != nil { return res, true, err } memberOf := []message.AttributeValue{} for i := range grps { if grps[i].GetName() == matchGroup { g := "cn=" + grps[i].GetName() + ",ou=groups," + strings.Join(s.nc, ",") memberOf = append(memberOf, message.AttributeValue(g)) } } if len(memberOf) > 0 { res.AddAttribute("memberOf", memberOf...) } else { return res, false, nil } entitySearchDN := "uid=" + e.GetID() + ",ou=entities," + strings.Join(s.nc, ",") s.routes.Search(s.handleSearchEntities). BaseDn(entitySearchDN). Scope(ldap.SearchRequestHomeSubtree). Label("Search - Entities (" + e.GetID() + ")") return res, true, nil } func (s *server) handleSearchGroups(w ldap.ResponseWriter, m *ldap.Message) { ctx := context.Background() s.l.Debug("Search Groups") r := m.GetSearchRequest() expr, exprAnd, err := s.buildBleveQuery(r.Filter()) if err != nil { // If err is non-nil at this point it must mean that // the above match didn't find a supported filter. s.l.Warn("Unsupported Search Filter, this is a bug, please file a report", "filter", r.Filter()) res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultUnwillingToPerform) res.SetDiagnosticMessage("Filter type not supported") w.Write(res) return } s.l.Debug("Searching groups", "expr", expr) var matchUid string if exprAnd { s.l.Debug("Filtering by AND...") exprSlice := strings.Split(expr, " ") for i := range exprSlice { if !strings.Contains(exprSlice[i], "Name") && !strings.Contains(exprSlice[i], "ID") { matchUid = exprSlice[i] if (i + 1) == len(exprSlice) { exprSlice = exprSlice[:i] } else { exprSlice = append(exprSlice[:i], exprSlice[i+1:]...) } break } } expr = strings.Join(exprSlice, " ") s.l.Debug("Filtered by AND", "expr", expr, "matchUid", matchUid) } groups, err := s.c.GroupSearch(ctx, expr) if err != nil { res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultOperationsError) res.SetDiagnosticMessage(err.Error()) w.Write(res) return } for i := range groups { s.l.Debug("Found group", "group", groups[i].GetName()) var e message.SearchResultEntry var complete bool var err error if exprAnd { s.l.Debug("Match UID: ", matchUid) e, complete, err = s.groupSearchResultWithMatch(ctx, groups[i], r.BaseObject(), r.Attributes(), matchUid) if !complete { continue } } else { e, err = s.groupSearchResult(ctx, groups[i], r.BaseObject(), r.Attributes()) } if err != nil { res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultOperationsError) res.SetDiagnosticMessage(err.Error()) w.Write(res) return } w.Write(e) } res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess) w.Write(res) } // groupSearchResult maps a group onto a SearchResultEntry, performing // the additional lookup for groups to populate the member attribute. // Though not implemented, the attrs list is plumbed down to this // level to permit attribute filtering in the future. func (s *server) groupSearchResult(ctx context.Context, g *pb.Group, dn message.LDAPDN, attrs message.AttributeSelection) (message.SearchResultEntry, error) { res := ldap.NewSearchResultEntry("cn=" + g.GetName() + "," + string(dn)) res.AddAttribute("cn", message.AttributeValue(g.GetName())) res.AddAttribute("gidNumber", message.AttributeValue(strconv.Itoa(int(g.GetNumber())))) members, err := s.c.GroupMembers(ctx, g.GetName()) if err != nil { return res, err } memberList := []message.AttributeValue{} for i := range members { g := "uid=" + members[i].GetID() + ",ou=entities," + strings.Join(s.nc, ",") memberList = append(memberList, message.AttributeValue(g)) } res.AddAttribute("member", memberList...) return res, nil } func (s *server) groupSearchResultWithMatch(ctx context.Context, g *pb.Group, dn message.LDAPDN, attrs message.AttributeSelection, matchUid string) (message.SearchResultEntry, bool, error) { res := ldap.NewSearchResultEntry("cn=" + g.GetName() + "," + string(dn)) res.AddAttribute("cn", message.AttributeValue(g.GetName())) res.AddAttribute("gidNumber", message.AttributeValue(strconv.Itoa(int(g.GetNumber())))) members, err := s.c.GroupMembers(ctx, g.GetName()) if err != nil { return res, true, err } var complete bool = false memberList := []message.AttributeValue{} for i := range members { if members[i].GetID() == matchUid { g := "uid=" + members[i].GetID() + ",ou=entities," + strings.Join(s.nc, ",") memberList = append(memberList, message.AttributeValue(g)) complete = true } } res.AddAttribute("member", memberList...) return res, complete, nil }