Initial QSfera import
This commit is contained in:
+491
@@ -0,0 +1,491 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Copyright 2021 The LibreGraph Authors.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ldapserver
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/go-logr/stdr"
|
||||
|
||||
"github.com/libregraph/idm/pkg/ldapdn"
|
||||
)
|
||||
|
||||
type Adder interface {
|
||||
Add(boundDN string, req *ldap.AddRequest, conn net.Conn) (LDAPResultCode, error)
|
||||
}
|
||||
|
||||
type Binder interface {
|
||||
Bind(bindDN, bindSimplePw string, conn net.Conn) (LDAPResultCode, error)
|
||||
}
|
||||
|
||||
type Deleter interface {
|
||||
Delete(boundDN string, req *ldap.DelRequest, conn net.Conn) (LDAPResultCode, error)
|
||||
}
|
||||
|
||||
type Modifier interface {
|
||||
Modify(boundDN string, req *ldap.ModifyRequest, conn net.Conn) (LDAPResultCode, error)
|
||||
}
|
||||
|
||||
type PasswordUpdater interface {
|
||||
ModifyPasswordExop(boundDN string, req *ldap.PasswordModifyRequest, conn net.Conn) (LDAPResultCode, error)
|
||||
}
|
||||
|
||||
type Renamer interface {
|
||||
ModifyDN(boundDN string, req *ldap.ModifyDNRequest, conn net.Conn) (LDAPResultCode, error)
|
||||
}
|
||||
|
||||
type Searcher interface {
|
||||
Search(boundDN string, req *ldap.SearchRequest, conn net.Conn) (ServerSearchResult, error)
|
||||
}
|
||||
|
||||
type Closer interface {
|
||||
Close(boundDN string, conn net.Conn) error
|
||||
}
|
||||
|
||||
var logger logr.Logger = stdr.New(log.Default())
|
||||
|
||||
type Server struct {
|
||||
AddFns map[string]Adder
|
||||
BindFns map[string]Binder
|
||||
DeleteFns map[string]Deleter
|
||||
ModifyFns map[string]Modifier
|
||||
ModifyDNFns map[string]Renamer
|
||||
PasswordExOpFns map[string]PasswordUpdater
|
||||
SearchFns map[string]Searcher
|
||||
CloseFns map[string]Closer
|
||||
Quit chan bool
|
||||
EnforceLDAP bool
|
||||
GeneratedPasswordLength int
|
||||
Stats *Stats
|
||||
}
|
||||
|
||||
type ServerSearchResult struct {
|
||||
Entries []*ldap.Entry
|
||||
Referrals []string
|
||||
Controls []ldap.Control
|
||||
ResultCode LDAPResultCode
|
||||
}
|
||||
|
||||
func NewServer() *Server {
|
||||
s := new(Server)
|
||||
s.Quit = make(chan bool)
|
||||
|
||||
d := defaultHandler{}
|
||||
s.AddFns = make(map[string]Adder)
|
||||
s.BindFns = make(map[string]Binder)
|
||||
s.DeleteFns = make(map[string]Deleter)
|
||||
s.ModifyFns = make(map[string]Modifier)
|
||||
s.ModifyDNFns = make(map[string]Renamer)
|
||||
s.PasswordExOpFns = make(map[string]PasswordUpdater)
|
||||
s.SearchFns = make(map[string]Searcher)
|
||||
s.CloseFns = make(map[string]Closer)
|
||||
s.BindFunc("", d)
|
||||
s.SearchFunc("", d)
|
||||
s.CloseFunc("", d)
|
||||
s.GeneratedPasswordLength = 16
|
||||
s.Stats = nil
|
||||
return s
|
||||
}
|
||||
|
||||
func Logger(l logr.Logger) {
|
||||
logger = l
|
||||
}
|
||||
|
||||
func (server *Server) AddFunc(baseDN string, f Adder) {
|
||||
server.AddFns[baseDN] = f
|
||||
}
|
||||
|
||||
func (server *Server) BindFunc(baseDN string, f Binder) {
|
||||
server.BindFns[baseDN] = f
|
||||
}
|
||||
|
||||
func (server *Server) DeleteFunc(baseDN string, f Deleter) {
|
||||
server.DeleteFns[baseDN] = f
|
||||
}
|
||||
|
||||
func (server *Server) ModifyFunc(baseDN string, f Modifier) {
|
||||
server.ModifyFns[baseDN] = f
|
||||
}
|
||||
|
||||
func (server *Server) ModifyDNFunc(baseDN string, f Renamer) {
|
||||
server.ModifyDNFns[baseDN] = f
|
||||
}
|
||||
|
||||
func (server *Server) PasswordExOpFunc(baseDN string, f PasswordUpdater) {
|
||||
server.PasswordExOpFns[baseDN] = f
|
||||
}
|
||||
|
||||
func (server *Server) SearchFunc(baseDN string, f Searcher) {
|
||||
server.SearchFns[baseDN] = f
|
||||
}
|
||||
|
||||
func (server *Server) CloseFunc(baseDN string, f Closer) {
|
||||
server.CloseFns[baseDN] = f
|
||||
}
|
||||
|
||||
func (server *Server) QuitChannel(quit chan bool) {
|
||||
server.Quit = quit
|
||||
}
|
||||
|
||||
func (server *Server) ListenAndServeTLS(listenString string, certFile string, keyFile string) error {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tlsConfig := tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
tlsConfig.ServerName = "localhost"
|
||||
ln, err := tls.Listen("tcp", listenString, &tlsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = server.Serve(ln)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *Server) SetStats(enable bool) {
|
||||
if enable {
|
||||
server.Stats = &Stats{}
|
||||
} else {
|
||||
server.Stats = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) GetStats() Stats {
|
||||
return *server.Stats.Clone()
|
||||
}
|
||||
|
||||
func (server *Server) ListenAndServe(listenString string) error {
|
||||
ln, err := net.Listen("tcp", listenString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = server.Serve(ln)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *Server) Serve(ln net.Listener) error {
|
||||
newConn := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
if !strings.HasSuffix(err.Error(), "use of closed network connection") {
|
||||
logger.Error(err, "Error accepting network connection")
|
||||
}
|
||||
break
|
||||
}
|
||||
logger.V(1).Info("New Connection", "addr", ln.Addr())
|
||||
newConn <- conn
|
||||
}
|
||||
}()
|
||||
|
||||
listener:
|
||||
for {
|
||||
select {
|
||||
case c := <-newConn:
|
||||
server.Stats.countConns(1)
|
||||
go server.handleConnection(c)
|
||||
case <-server.Quit:
|
||||
ln.Close()
|
||||
break listener
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *Server) handleConnection(conn net.Conn) {
|
||||
boundDN := "" // "" == anonymous
|
||||
|
||||
handler:
|
||||
for {
|
||||
// Read incoming LDAP packet.
|
||||
packet, err := ber.ReadPacket(conn)
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF { // Client closed connection.
|
||||
break
|
||||
} else if err != nil {
|
||||
logger.Error(err, "handleConnection ber.ReadPacket")
|
||||
break
|
||||
}
|
||||
|
||||
// Sanity check this packet.
|
||||
if len(packet.Children) < 2 {
|
||||
logger.V(1).Info("len(packet.Children) < 2")
|
||||
break
|
||||
}
|
||||
// Check the message ID and ClassType.
|
||||
messageID, ok := packet.Children[0].Value.(int64)
|
||||
if !ok {
|
||||
logger.V(1).Info("malformed messageID")
|
||||
break
|
||||
}
|
||||
req := packet.Children[1]
|
||||
if req.ClassType != ber.ClassApplication {
|
||||
logger.V(1).Info("req.ClassType != ber.ClassApplication")
|
||||
break
|
||||
}
|
||||
// Handle controls if present.
|
||||
controls := []ldap.Control{}
|
||||
if len(packet.Children) > 2 {
|
||||
for _, child := range packet.Children[2].Children {
|
||||
c, err := ldap.DecodeControl(child)
|
||||
if err != nil {
|
||||
logger.Error(err, "handleConnection decode control")
|
||||
continue
|
||||
}
|
||||
controls = append(controls, c)
|
||||
}
|
||||
}
|
||||
|
||||
// log.Printf("DEBUG: handling operation: %s [%d]", ldap.ApplicationMap[uint8(req.Tag)], req.Tag)
|
||||
// ber.PrintPacket(packet) // DEBUG
|
||||
|
||||
// Dispatch the LDAP operation.
|
||||
switch req.Tag { // LDAP op code.
|
||||
default:
|
||||
op, ok := ldap.ApplicationMap[uint8(req.Tag)]
|
||||
if !ok {
|
||||
op = "unknown"
|
||||
}
|
||||
|
||||
logger.V(1).Info("Unhandled operation", "type", op, "tag", req.Tag)
|
||||
break handler
|
||||
|
||||
case ldap.ApplicationAddRequest:
|
||||
server.Stats.countAdds(1)
|
||||
|
||||
resultCode := uint16(ldap.LDAPResultSuccess)
|
||||
resultMsg := ""
|
||||
if err = HandleAddRequest(req, boundDN, server, conn); err != nil {
|
||||
var lErr *ldap.Error
|
||||
if errors.As(err, &lErr) {
|
||||
resultCode = lErr.ResultCode
|
||||
if lErr.Err != nil {
|
||||
resultMsg = lErr.Err.Error()
|
||||
}
|
||||
} else {
|
||||
resultCode = ldap.LDAPResultOperationsError
|
||||
resultMsg = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
responsePacket := encodeLDAPResponse(messageID, ldap.ApplicationAddResponse, LDAPResultCode(resultCode), resultMsg)
|
||||
if err = sendPacket(conn, responsePacket); err != nil {
|
||||
logger.Error(err, "sendPacket error")
|
||||
break handler
|
||||
}
|
||||
|
||||
case ldap.ApplicationBindRequest:
|
||||
server.Stats.countBinds(1)
|
||||
ldapResultCode := HandleBindRequest(req, server.BindFns, conn)
|
||||
if ldapResultCode == ldap.LDAPResultSuccess {
|
||||
boundDN, ok = req.Children[1].Value.(string)
|
||||
if !ok {
|
||||
logger.V(1).Info("Malformed Bind DN")
|
||||
break handler
|
||||
}
|
||||
if boundDN, err = ldapdn.ParseNormalize(boundDN); err != nil {
|
||||
logger.V(1).Info("Error normalizing Bind DN", "error", err.Error())
|
||||
break handler
|
||||
}
|
||||
|
||||
}
|
||||
responsePacket := encodeBindResponse(messageID, ldapResultCode)
|
||||
if err = sendPacket(conn, responsePacket); err != nil {
|
||||
logger.Error(err, "sendPacket error")
|
||||
break handler
|
||||
}
|
||||
|
||||
case ldap.ApplicationCompareRequest:
|
||||
responsePacket := encodeLDAPResponse(messageID, ldap.ApplicationCompareRequest, ldap.LDAPResultOperationsError, "Unsupported operation: compare")
|
||||
if err = sendPacket(conn, responsePacket); err != nil {
|
||||
logger.Error(err, "sendPacket error")
|
||||
}
|
||||
logger.V(1).Info("Unhandled operation", "type", ldap.ApplicationMap[uint8(req.Tag)], "tag", req.Tag)
|
||||
break handler
|
||||
|
||||
case ldap.ApplicationDelRequest:
|
||||
server.Stats.countDeletes(1)
|
||||
resultCode := uint16(ldap.LDAPResultSuccess)
|
||||
resultMsg := ""
|
||||
if err = HandleDeleteRequest(req, boundDN, server, conn); err != nil {
|
||||
var lErr *ldap.Error
|
||||
if errors.As(err, &lErr) {
|
||||
resultCode = lErr.ResultCode
|
||||
if lErr.Err != nil {
|
||||
resultMsg = lErr.Err.Error()
|
||||
}
|
||||
} else {
|
||||
resultCode = ldap.LDAPResultOperationsError
|
||||
resultMsg = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
responsePacket := encodeLDAPResponse(messageID, ldap.ApplicationDelResponse, LDAPResultCode(resultCode), resultMsg)
|
||||
if err = sendPacket(conn, responsePacket); err != nil {
|
||||
logger.Error(err, "sendPacket error")
|
||||
break handler
|
||||
}
|
||||
|
||||
case ldap.ApplicationExtendedRequest:
|
||||
resultCode := uint16(ldap.LDAPResultSuccess)
|
||||
resultMsg := ""
|
||||
var innerBer, responsePacket *ber.Packet
|
||||
if innerBer, err = HandleExtendedRequest(req, boundDN, server, conn); err != nil {
|
||||
resultCode = ldap.LDAPResultOperationsError
|
||||
resultMsg = err.Error()
|
||||
responsePacket = encodeLDAPResponse(messageID, ldap.ApplicationExtendedResponse, LDAPResultCode(resultCode), resultMsg)
|
||||
} else {
|
||||
responsePacket = ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Response")
|
||||
responsePacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "Message ID"))
|
||||
responsePacket.AppendChild(innerBer)
|
||||
}
|
||||
|
||||
if err = sendPacket(conn, responsePacket); err != nil {
|
||||
logger.Error(err, "sendPacket error")
|
||||
break handler
|
||||
}
|
||||
|
||||
case ldap.ApplicationModifyDNRequest:
|
||||
server.Stats.countModifyDNs(1)
|
||||
resultCode := uint16(ldap.LDAPResultSuccess)
|
||||
resultMsg := ""
|
||||
if err = HandleModifyDNRequest(req, boundDN, server, conn); err != nil {
|
||||
var lErr *ldap.Error
|
||||
if errors.As(err, &lErr) {
|
||||
resultCode = lErr.ResultCode
|
||||
if lErr.Err != nil {
|
||||
resultMsg = lErr.Err.Error()
|
||||
}
|
||||
} else {
|
||||
resultCode = ldap.LDAPResultOperationsError
|
||||
resultMsg = err.Error()
|
||||
}
|
||||
}
|
||||
responsePacket := encodeLDAPResponse(messageID, ldap.ApplicationModifyDNResponse, LDAPResultCode(resultCode), resultMsg)
|
||||
if err = sendPacket(conn, responsePacket); err != nil {
|
||||
logger.Error(err, "sendPacket error")
|
||||
break handler
|
||||
}
|
||||
|
||||
case ldap.ApplicationModifyRequest:
|
||||
server.Stats.countModifies(1)
|
||||
resultCode := uint16(ldap.LDAPResultSuccess)
|
||||
resultMsg := ""
|
||||
if err = HandleModifyRequest(req, boundDN, server, conn); err != nil {
|
||||
var lErr *ldap.Error
|
||||
if errors.As(err, &lErr) {
|
||||
resultCode = lErr.ResultCode
|
||||
if lErr.Err != nil {
|
||||
resultMsg = lErr.Err.Error()
|
||||
}
|
||||
} else {
|
||||
resultCode = ldap.LDAPResultOperationsError
|
||||
resultMsg = err.Error()
|
||||
}
|
||||
}
|
||||
responsePacket := encodeLDAPResponse(messageID, ldap.ApplicationModifyResponse, LDAPResultCode(resultCode), resultMsg)
|
||||
if err = sendPacket(conn, responsePacket); err != nil {
|
||||
logger.Error(err, "sendPacket error")
|
||||
break handler
|
||||
}
|
||||
|
||||
case ldap.ApplicationSearchRequest:
|
||||
server.Stats.countSearches(1)
|
||||
if doneControls, err := HandleSearchRequest(req, &controls, messageID, boundDN, server, conn); err != nil {
|
||||
// TODO: make this more testable/better err handling - stop using log, stop using breaks?
|
||||
logger.V(1).Info("handleSearchRequest", "error", err.Error())
|
||||
e := err.(*ldap.Error)
|
||||
if err = sendPacket(conn, encodeSearchDone(messageID, LDAPResultCode(e.ResultCode), doneControls)); err != nil {
|
||||
logger.Error(err, "sendPacket error")
|
||||
break handler
|
||||
}
|
||||
break handler
|
||||
} else {
|
||||
if err = sendPacket(conn, encodeSearchDone(messageID, ldap.LDAPResultSuccess, doneControls)); err != nil {
|
||||
logger.Error(err, "sendPacket error")
|
||||
break handler
|
||||
}
|
||||
}
|
||||
case ldap.ApplicationUnbindRequest:
|
||||
server.Stats.countUnbinds(1)
|
||||
break handler // Simply disconnect.
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range server.CloseFns {
|
||||
c.Close(boundDN, conn)
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
server.Stats.countConnsClose(1)
|
||||
}
|
||||
|
||||
func sendPacket(conn net.Conn, packet *ber.Packet) error {
|
||||
_, err := conn.Write(packet.Bytes())
|
||||
if err != nil {
|
||||
logger.Error(err, "Error Sending Message")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func routeFunc(dn string, funcNames []string) string {
|
||||
bestPick := ""
|
||||
for _, fn := range funcNames {
|
||||
if strings.HasSuffix(dn, fn) {
|
||||
l := len(strings.Split(bestPick, ","))
|
||||
if bestPick == "" {
|
||||
l = 0
|
||||
}
|
||||
if len(strings.Split(fn, ",")) > l {
|
||||
bestPick = fn
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestPick
|
||||
}
|
||||
|
||||
func encodeLDAPResponse(messageID int64, responseType uint8, ldapResultCode LDAPResultCode, message string) *ber.Packet {
|
||||
responsePacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Response")
|
||||
responsePacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "Message ID"))
|
||||
response := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ber.Tag(responseType), nil, ldap.ApplicationMap[responseType])
|
||||
response.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(ldapResultCode), "resultCode: "))
|
||||
response.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "matchedDN: "))
|
||||
response.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, message, "errorMessage: "))
|
||||
responsePacket.AppendChild(response)
|
||||
return responsePacket
|
||||
}
|
||||
|
||||
type defaultHandler struct {
|
||||
}
|
||||
|
||||
func (h defaultHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (LDAPResultCode, error) {
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
func (h defaultHandler) Search(boundDN string, req *ldap.SearchRequest, conn net.Conn) (ServerSearchResult, error) {
|
||||
return ServerSearchResult{make([]*ldap.Entry, 0), []string{}, []ldap.Control{}, ldap.LDAPResultSuccess}, nil
|
||||
}
|
||||
|
||||
func (h defaultHandler) Close(boundDN string, conn net.Conn) error {
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user