2020-08-21 02:03:53 -05:00
|
|
|
package ldap
|
|
|
|
|
|
|
|
import (
|
2020-08-23 02:14:41 -05:00
|
|
|
"context"
|
|
|
|
"errors"
|
2020-08-23 21:35:21 -05:00
|
|
|
"fmt"
|
2020-08-23 02:14:41 -05:00
|
|
|
"strconv"
|
2020-08-22 22:49:54 -05:00
|
|
|
"strings"
|
2020-08-21 02:03:53 -05:00
|
|
|
|
2020-08-22 22:49:54 -05:00
|
|
|
"github.com/ps78674/goldap/message"
|
2020-08-21 02:03:53 -05:00
|
|
|
ldap "github.com/ps78674/ldapserver"
|
2020-08-22 22:49:54 -05:00
|
|
|
|
|
|
|
"github.com/netauth/ldap/internal/buildinfo"
|
2020-08-23 02:14:41 -05:00
|
|
|
|
|
|
|
pb "github.com/netauth/protocol"
|
2020-08-21 02:03:53 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
func (s *server) handleSearchDSE(w ldap.ResponseWriter, m *ldap.Message) {
|
2020-08-22 22:49:54 -05:00
|
|
|
nc := strings.Join(s.nc, ", ")
|
|
|
|
|
2020-08-21 02:03:53 -05:00
|
|
|
e := ldap.NewSearchResultEntry("")
|
|
|
|
e.AddAttribute("vendorName", "NetAuth")
|
2020-08-22 22:49:54 -05:00
|
|
|
e.AddAttribute("vendorVersion", message.AttributeValue(buildinfo.Version))
|
2020-08-21 02:03:53 -05:00
|
|
|
e.AddAttribute("objectClass", "top", "extensibleObject")
|
|
|
|
e.AddAttribute("supportedLDAPVersion", "3")
|
2020-08-22 22:49:54 -05:00
|
|
|
e.AddAttribute("namingContexts", message.AttributeValue(nc))
|
2020-08-21 02:03:53 -05:00
|
|
|
w.Write(e)
|
|
|
|
|
|
|
|
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess)
|
|
|
|
w.Write(res)
|
|
|
|
}
|
|
|
|
|
2020-08-23 02:14:41 -05:00
|
|
|
func (s *server) handleSearchEntities(w ldap.ResponseWriter, m *ldap.Message) {
|
|
|
|
ctx := context.Background()
|
|
|
|
s.l.Debug("Search Entities")
|
|
|
|
|
2020-08-21 02:03:53 -05:00
|
|
|
r := m.GetSearchRequest()
|
2020-08-23 02:14:41 -05:00
|
|
|
|
|
|
|
// This switch performs stage one of mapping from an ldap
|
|
|
|
// search expression to a NetAuth search expression. The
|
|
|
|
// second phase of the mapping happens in another function.
|
|
|
|
var expr string
|
|
|
|
var err error
|
|
|
|
switch r.Filter().(type) {
|
|
|
|
case message.FilterEqualityMatch:
|
|
|
|
f := r.Filter().(message.FilterEqualityMatch)
|
|
|
|
expr, err = entitySearchExprHelper(string(f.AttributeDesc()), "=", string(f.AssertionValue()))
|
2020-08-21 02:03:53 -05:00
|
|
|
default:
|
2020-08-23 17:11:14 -05:00
|
|
|
s.l.Warn("Unsupported entity search filter", "type", fmt.Sprintf("%T", r.Filter()))
|
|
|
|
s.l.Debug("Unsupported search filter", "filter", r.FilterString())
|
2020-08-23 02:14:41 -05:00
|
|
|
err = errors.New("unsupported filter type")
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
// If err is non-nil at this point it must mean that
|
|
|
|
// the above match didn't find a supported filter.
|
|
|
|
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultUnwillingToPerform)
|
|
|
|
res.SetDiagnosticMessage("Filter type not supported")
|
|
|
|
w.Write(res)
|
|
|
|
return
|
2020-08-21 02:03:53 -05:00
|
|
|
}
|
|
|
|
|
2020-08-23 02:14:41 -05:00
|
|
|
s.l.Debug("Searching entities", "query", expr)
|
2020-08-21 02:03:53 -05:00
|
|
|
|
2020-08-23 02:14:41 -05:00
|
|
|
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 {
|
|
|
|
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)
|
2020-08-21 02:03:53 -05:00
|
|
|
|
|
|
|
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess)
|
|
|
|
w.Write(res)
|
|
|
|
}
|
2020-08-23 02:14:41 -05:00
|
|
|
|
|
|
|
// 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) {
|
|
|
|
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()))))
|
|
|
|
|
|
|
|
grps, err := s.c.EntityGroups(ctx, e.GetID())
|
|
|
|
if err != nil {
|
|
|
|
return res, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range grps {
|
|
|
|
g := "cn=" + grps[i].GetName() + ",ou=groups," + strings.Join(s.nc, ",")
|
|
|
|
res.AddAttribute("memberOf", message.AttributeValue(g))
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// entitySearchExprHelper helps in mapping ldap search expressions to
|
|
|
|
// search expressions that NetAuth understands.
|
|
|
|
func entitySearchExprHelper(attr, op, val string) (string, error) {
|
|
|
|
var predicate, operator string
|
|
|
|
|
|
|
|
switch attr {
|
|
|
|
case "uid":
|
|
|
|
predicate = "ID"
|
|
|
|
default:
|
|
|
|
return "", errors.New("search attribute is unsupported")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch op {
|
|
|
|
case "=":
|
|
|
|
operator = "="
|
|
|
|
default:
|
|
|
|
return "", errors.New("search comparison is unsupported")
|
|
|
|
}
|
|
|
|
|
|
|
|
return predicate + operator + val, nil
|
|
|
|
}
|
2020-08-23 02:50:43 -05:00
|
|
|
|
|
|
|
func (s *server) handleSearchGroups(w ldap.ResponseWriter, m *ldap.Message) {
|
|
|
|
ctx := context.Background()
|
|
|
|
s.l.Debug("Search Groups")
|
|
|
|
|
|
|
|
r := m.GetSearchRequest()
|
|
|
|
|
|
|
|
// This switch performs stage one of mapping from an ldap
|
|
|
|
// search expression to a NetAuth search expression. The
|
|
|
|
// second phase of the mapping happens in another function.
|
|
|
|
var expr string
|
|
|
|
var err error
|
|
|
|
switch r.Filter().(type) {
|
|
|
|
case message.FilterEqualityMatch:
|
|
|
|
f := r.Filter().(message.FilterEqualityMatch)
|
|
|
|
expr, err = groupSearchExprHelper(string(f.AttributeDesc()), "=", string(f.AssertionValue()))
|
|
|
|
default:
|
2020-08-23 17:11:14 -05:00
|
|
|
s.l.Warn("Unsupported group search filter", "type", fmt.Sprintf("%T", r.Filter()))
|
|
|
|
s.l.Debug("Unsupported search filter", "filter", r.FilterString())
|
2020-08-23 02:50:43 -05:00
|
|
|
err = errors.New("unsupported filter type")
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
// If err is non-nil at this point it must mean that
|
|
|
|
// the above match didn't find a supported filter.
|
|
|
|
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultUnwillingToPerform)
|
|
|
|
res.SetDiagnosticMessage("Filter type not supported")
|
|
|
|
w.Write(res)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
s.l.Debug("Searching groups", "expr", expr)
|
|
|
|
|
|
|
|
members, 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 members {
|
|
|
|
e, err := s.groupSearchResult(ctx, members[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
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range members {
|
|
|
|
g := "uid=" + members[i].GetID() + ",ou=entities," + strings.Join(s.nc, ",")
|
|
|
|
res.AddAttribute("member", message.AttributeValue(g))
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// groupSearchExprHelper helps in mapping ldap search expressions to
|
|
|
|
// search expressions that NetAuth understands.
|
|
|
|
func groupSearchExprHelper(attr, op, val string) (string, error) {
|
|
|
|
var predicate, operator string
|
|
|
|
|
|
|
|
switch attr {
|
|
|
|
case "cn":
|
|
|
|
predicate = "name"
|
|
|
|
default:
|
|
|
|
return "", errors.New("search attribute is unsupported")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch op {
|
|
|
|
case "=":
|
|
|
|
operator = "="
|
|
|
|
default:
|
|
|
|
return "", errors.New("search comparison is unsupported")
|
|
|
|
}
|
|
|
|
|
|
|
|
return predicate + operator + val, nil
|
|
|
|
}
|