Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Copyright (c) 2021 The LibreGraph Authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+21
View File
@@ -0,0 +1,21 @@
# LDAP server library for Golang
This library provides LDAP server v3 functionality for the GO programming
language.
The server implementation is based on github.com/nmcclain/ldap and is enhanced
so it can be used together with github.com/go-ldap/ldap/v3.
From the server perspective, all of RFC4510 is implemented except:
4.5.1.3. SearchRequest.derefAliases
4.5.1.5. SearchRequest.timeLimit
4.5.1.6. SearchRequest.typesOnly
4.14. StartTLS Operation
The purpose of this library is not a general LDAP server implementation but to
provide enough of an LDAP server for Kopano compatible identity management.
## License
See `LICENSE.txt` for licensing information of this module.
+127
View File
@@ -0,0 +1,127 @@
package ldapserver
import (
"errors"
"fmt"
"net"
ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3"
)
func HandleAddRequest(req *ber.Packet, boundDN string, server *Server, conn net.Conn) error {
if boundDN == "" {
return ldap.NewError(ldap.LDAPResultInsufficientAccessRights, errors.New("anonymous Write denied"))
}
addReq, err := parseAddRequest(req)
if err != nil {
return err
}
fnNames := []string{}
for k := range server.AddFns {
fnNames = append(fnNames, k)
}
fn := routeFunc(addReq.DN, fnNames)
var adder Adder
if adder = server.AddFns[fn]; adder == nil {
if fn == "" {
err = fmt.Errorf("no suitable handler found for dn: '%s'", addReq.DN)
} else {
err = fmt.Errorf("handler '%s' does not support add", fn)
}
return ldap.NewError(ldap.LDAPResultUnwillingToPerform, err)
}
code, err := adder.Add(boundDN, addReq, conn)
return ldap.NewError(uint16(code), err)
}
func parseAddRequest(req *ber.Packet) (*ldap.AddRequest, error) {
addReq := ldap.AddRequest{}
// LDAP Add request have 2 Elements (DN and AttributeList)
if len(req.Children) != 2 {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("invalid add request"))
}
dn, ok := req.Children[0].Value.(string)
if !ok {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding entry DN"))
}
_, err := ldap.ParseDN(dn)
if err != nil {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, err)
}
addReq.DN = dn
al, err := parseAttributeList(req.Children[1])
if err != nil {
return nil, err
}
addReq.Attributes = al
return &addReq, nil
}
func parseAttributeList(req *ber.Packet) ([]ldap.Attribute, error) {
ldapAttrs := []ldap.Attribute{}
if req.ClassType != ber.ClassUniversal || req.TagType != ber.TypeConstructed || req.Tag != ber.TagSequence {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding Attribute List"))
}
for _, a := range req.Children {
attr, err := parseAttribute(a, false)
if err != nil {
return nil, err
}
ldapAttrs = append(ldapAttrs, *attr)
}
return ldapAttrs, nil
}
func parseAttribute(attr *ber.Packet, partial bool) (*ldap.Attribute, error) {
var la ldap.Attribute
var ok bool
var err error
// Partial attributes, might just contain a type and allow the Value to be absent
if partial && (len(attr.Children) < 1 || len(attr.Children) > 2) {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding partial Attribute"))
} else if !partial && len(attr.Children) != 2 {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding Attribute"))
}
ad := attr.Children[0]
if ad.ClassType != ber.ClassUniversal || ad.TagType != ber.TypePrimitive || ad.Tag != ber.TagOctetString {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding Attribute Description"))
}
la.Type, ok = ad.Value.(string)
if !ok {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding Attribute Description"))
}
// We can return here if this is a Partial Attribute without values
if partial && len(attr.Children) == 1 {
return &la, nil
}
if la.Vals, err = parseAttributeValues(attr.Children[1]); err != nil {
return nil, err
}
return &la, nil
}
func parseAttributeValues(values *ber.Packet) ([]string, error) {
var strVals []string
if values.ClassType != ber.ClassUniversal || values.TagType != ber.TypeConstructed || values.Tag != ber.TagSet {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding Attribute Values"))
}
for _, value := range values.Children {
strVal, ok := value.Value.(string)
if !ok {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding Attribute Value"))
}
strVals = append(strVals, strVal)
}
return strVals, nil
}
+79
View File
@@ -0,0 +1,79 @@
// Copyright 2012 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 (
"net"
ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3"
)
func HandleBindRequest(req *ber.Packet, fns map[string]Binder, conn net.Conn) (resultCode LDAPResultCode) {
defer func() {
if r := recover(); r != nil {
resultCode = ldap.LDAPResultOperationsError
}
}()
// we only support ldapv3
ldapVersion, ok := req.Children[0].Value.(int64)
if !ok {
return ldap.LDAPResultProtocolError
}
if ldapVersion != 3 {
logger.V(1).Info("Unsupported LDAP version", "version", ldapVersion)
return ldap.LDAPResultInappropriateAuthentication
}
// auth types
bindDN, ok := req.Children[1].Value.(string)
if !ok {
return ldap.LDAPResultProtocolError
}
bindAuth := req.Children[2]
switch bindAuth.Tag {
default:
logger.V(1).Info("Unknown LDAP authentication method", "tag", bindAuth.Tag)
return ldap.LDAPResultInappropriateAuthentication
case LDAPBindAuthSimple:
if len(req.Children) == 3 {
fnNames := []string{}
for k := range fns {
fnNames = append(fnNames, k)
}
fn := routeFunc(bindDN, fnNames)
resultCode, err := fns[fn].Bind(bindDN, bindAuth.Data.String(), conn)
if err != nil {
logger.Error(err, "BindFn Error")
return ldap.LDAPResultOperationsError
}
return resultCode
} else {
logger.V(1).Info("Simple bind request has wrong # children. len(req.Children) != 3")
return ldap.LDAPResultInappropriateAuthentication
}
case LDAPBindAuthSASL:
logger.V(1).Info("SASL authentication is not supported")
return ldap.LDAPResultInappropriateAuthentication
}
return ldap.LDAPResultOperationsError
}
func encodeBindResponse(messageID int64, ldapResultCode LDAPResultCode) *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"))
bindReponse := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ldap.ApplicationBindResponse, nil, "Bind Response")
bindReponse.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(ldapResultCode), "resultCode: "))
bindReponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "matchedDN: "))
bindReponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "errorMessage: "))
responsePacket.AppendChild(bindReponse)
// ber.PrintPacket(responsePacket)
return responsePacket
}
+54
View File
@@ -0,0 +1,54 @@
package ldapserver
import (
"errors"
"fmt"
"net"
ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3"
)
func HandleDeleteRequest(req *ber.Packet, boundDN string, server *Server, conn net.Conn) error {
if boundDN == "" {
return ldap.NewError(ldap.LDAPResultInsufficientAccessRights, errors.New("anonymous Write denied"))
}
delReq, err := parseDeleteRequest(req)
if err != nil {
return err
}
fnNames := []string{}
for k := range server.DeleteFns {
fnNames = append(fnNames, k)
}
fn := routeFunc(delReq.DN, fnNames)
var del Deleter
if del = server.DeleteFns[fn]; del == nil {
if fn == "" {
err = fmt.Errorf("no suitable handler found for dn: '%s'", delReq.DN)
} else {
err = fmt.Errorf("handler '%s' does not support add", fn)
}
return ldap.NewError(ldap.LDAPResultUnwillingToPerform, err)
}
code, err := del.Delete(boundDN, delReq, conn)
return ldap.NewError(uint16(code), err)
}
func parseDeleteRequest(req *ber.Packet) (*ldap.DelRequest, error) {
delReq := ldap.DelRequest{}
// LDAP Delete requests contain just the DN (no Sequence, or set)
// i.e. they have no childre
if len(req.Children) != 0 {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("invalid delete request"))
}
dn := req.Data.String()
_, err := ldap.ParseDN(dn)
if err != nil {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, err)
}
delReq.DN = dn
return &delReq, nil
}
+90
View File
@@ -0,0 +1,90 @@
package ldapserver
import (
"errors"
"net"
ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3"
)
type ExopHandler func(req *ber.Packet, boundDN string, server *Server, conn net.Conn) (*ber.Packet, error)
type ExtendedRequest struct {
OID string
Body *ber.Packet
}
var exopRegistry = map[string]ExopHandler{}
func RegisterExtendedOperation(oid string, handler ExopHandler) {
exopRegistry[oid] = handler
}
func HandleExtendedRequest(req *ber.Packet, boundDN string, server *Server, conn net.Conn) (*ber.Packet, error) {
extReq, err := parseExtendedRequest(req)
if err != nil {
logger.V(1).Info("parsing extened request failed", "error", err)
return nil, err
}
if handler, ok := exopRegistry[extReq.OID]; ok {
innerBer, err := handler(extReq.Body, boundDN, server, conn)
var resCode LDAPResultCode = ldap.LDAPResultSuccess
msg := ""
if err != nil {
if lerr, ok := err.(*ldap.Error); ok {
msg = lerr.Err.Error()
resCode = LDAPResultCode(lerr.ResultCode)
} else {
msg = err.Error()
resCode = ldap.LDAPResultOther
}
}
return encodeExtendedResponse(resCode, msg, extReq.OID, innerBer), nil
} else {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("Unsupported Extented Operations"))
}
return nil, err
}
func parseExtendedRequest(req *ber.Packet) (*ExtendedRequest, error) {
// RFC 4511 Extended Operation:
// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
// requestName [0] LDAPOID,
// requestValue [1] OCTET STRING OPTIONAL }
extReq := ExtendedRequest{}
if len(req.Children) < 1 || len(req.Children) > 2 {
return nil, ldap.NewError(ldap.LDAPResultDecodingError, errors.New("invalid extented request"))
}
if req.Children[0].Identifier.ClassType != ber.ClassContext || req.Children[0].Identifier.Tag != 0 {
return nil, ldap.NewError(ldap.LDAPResultDecodingError, errors.New("error decoding extented request OID"))
}
extReq.OID = req.Children[0].Data.String()
if len(req.Children) == 2 {
if req.Children[1].Identifier.ClassType != ber.ClassContext || req.Children[1].Identifier.Tag != 1 {
return nil, ldap.NewError(ldap.LDAPResultDecodingError, errors.New("error decoding extented request OID"))
}
extReq.Body = req.Children[1]
}
logger.V(1).Info("Extended Request", "oid", extReq.OID)
return &extReq, nil
}
func encodeExtendedResponse(rescode LDAPResultCode, msg, oid string, responseValue *ber.Packet) *ber.Packet {
respBer := ber.Encode(ber.ClassApplication, ber.TypeConstructed,
ber.Tag(ldap.ApplicationExtendedResponse), nil,
ldap.ApplicationMap[ldap.ApplicationExtendedResponse])
respBer.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(rescode), "resultCode: "))
respBer.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "matchedDN: "))
respBer.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, msg, "errorMessage: "))
respBer.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 10, oid, "responseName"))
if responseValue != nil {
encValue := ber.Encode(ber.ClassContext, ber.TypePrimitive, 11, nil, "responseValue")
encValue.AppendChild(responseValue)
respBer.AppendChild(encValue)
}
return respBer
}
+193
View File
@@ -0,0 +1,193 @@
// 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 (
"strings"
ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3"
"golang.org/x/text/cases"
)
const (
FilterAnd = ldap.FilterAnd
FilterOr = ldap.FilterOr
FilterNot = ldap.FilterNot
FilterEqualityMatch = ldap.FilterEqualityMatch
FilterSubstrings = ldap.FilterSubstrings
FilterGreaterOrEqual = ldap.FilterGreaterOrEqual
FilterLessOrEqual = ldap.FilterLessOrEqual
FilterPresent = ldap.FilterPresent
FilterApproxMatch = ldap.FilterApproxMatch
FilterExtensibleMatch = ldap.FilterExtensibleMatch
)
var (
FilterMap = ldap.FilterMap
casefold = cases.Fold()
)
const (
FilterSubstringsInitial = ldap.FilterSubstringsInitial
FilterSubstringsAny = ldap.FilterSubstringsAny
FilterSubstringsFinal = ldap.FilterSubstringsFinal
)
func CompileFilter(filter string) (*ber.Packet, error) {
return ldap.CompileFilter(filter)
}
func DecompileFilter(packet *ber.Packet) (ret string, err error) {
return ldap.DecompileFilter(packet)
}
func ServerApplyFilter(f *ber.Packet, entry *ldap.Entry) (bool, LDAPResultCode) {
switch FilterMap[uint64(f.Tag)] {
default:
//log.Fatalf("Unknown LDAP filter code: %d", f.Tag)
return false, ldap.LDAPResultOperationsError
case "Equality Match":
if len(f.Children) != 2 {
return false, ldap.LDAPResultOperationsError
}
attribute := f.Children[0].Value.(string)
value := f.Children[1].Value.(string)
for _, a := range entry.Attributes {
if strings.EqualFold(a.Name, attribute) {
for _, v := range a.Values {
if strings.EqualFold(v, value) {
return true, ldap.LDAPResultSuccess
}
}
}
}
case "Present":
for _, a := range entry.Attributes {
if strings.EqualFold(a.Name, f.Data.String()) {
return true, ldap.LDAPResultSuccess
}
}
case "And":
for _, child := range f.Children {
ok, exitCode := ServerApplyFilter(child, entry)
if exitCode != ldap.LDAPResultSuccess {
return false, exitCode
}
if !ok {
return false, ldap.LDAPResultSuccess
}
}
return true, ldap.LDAPResultSuccess
case "Or":
anyOk := false
for _, child := range f.Children {
ok, exitCode := ServerApplyFilter(child, entry)
if exitCode != ldap.LDAPResultSuccess {
return false, exitCode
} else if ok {
anyOk = true
}
}
if anyOk {
return true, ldap.LDAPResultSuccess
}
case "Not":
if len(f.Children) != 1 {
return false, ldap.LDAPResultOperationsError
}
ok, exitCode := ServerApplyFilter(f.Children[0], entry)
if exitCode != ldap.LDAPResultSuccess {
return false, exitCode
} else if !ok {
return true, ldap.LDAPResultSuccess
}
case "Substrings":
if len(f.Children) != 2 {
return false, ldap.LDAPResultOperationsError
}
attribute := f.Children[0].Value.(string)
bytes := f.Children[1].Children[0].Data.Bytes()
value := casefold.String(string(bytes))
for _, a := range entry.Attributes {
if strings.EqualFold(a.Name, attribute) {
for _, v := range a.Values {
v = casefold.String(v)
switch f.Children[1].Children[0].Tag {
case FilterSubstringsInitial:
if strings.HasPrefix(v, value) {
return true, ldap.LDAPResultSuccess
}
case FilterSubstringsAny:
if strings.Contains(v, value) {
return true, ldap.LDAPResultSuccess
}
case FilterSubstringsFinal:
if strings.HasSuffix(v, value) {
return true, ldap.LDAPResultSuccess
}
}
}
}
}
case "FilterGreaterOrEqual": // TODO
return false, ldap.LDAPResultOperationsError
case "FilterLessOrEqual": // TODO
return false, ldap.LDAPResultOperationsError
case "FilterApproxMatch": // TODO
return false, ldap.LDAPResultOperationsError
case "FilterExtensibleMatch": // TODO
return false, ldap.LDAPResultOperationsError
}
return false, ldap.LDAPResultSuccess
}
func ServerFilterScope(baseDN string, scope int, entry *ldap.Entry) (bool, LDAPResultCode) {
// constrained search scope
parsedBaseDn, err := ldap.ParseDN(baseDN)
if err != nil {
return false, ldap.LDAPResultOperationsError
}
parsedDn, err := ldap.ParseDN(entry.DN)
if err != nil {
return false, ldap.LDAPResultOperationsError
}
switch scope {
case ldap.ScopeWholeSubtree: // The scope is constrained to the entry named by baseObject and to all its subordinates.
case ldap.ScopeBaseObject: // The scope is constrained to the entry named by baseObject.
if !parsedDn.EqualFold(parsedBaseDn) {
return false, ldap.LDAPResultSuccess
}
case ldap.ScopeSingleLevel: // The scope is constrained to the immediate subordinates of the entry named by baseObject.
parts := strings.Split(entry.DN, ",")
if len(parts) < 2 && !parsedDn.EqualFold(parsedBaseDn) {
return false, ldap.LDAPResultSuccess
}
subDn := strings.Join(parts[1:], ",")
parsedSubDn, err := ldap.ParseDN(subDn)
if err != nil {
return false, ldap.LDAPResultOperationsError
}
if !parsedSubDn.EqualFold(parsedBaseDn) {
return false, ldap.LDAPResultSuccess
}
}
return true, ldap.LDAPResultSuccess
}
func ServerFilterAttributes(attributes []string, entry *ldap.Entry) (LDAPResultCode, error) {
// attributes
if len(attributes) > 1 || (len(attributes) == 1 && len(attributes[0]) > 0) {
_, err := filterAttributes(entry, attributes)
if err != nil {
return ldap.LDAPResultOperationsError, err
}
}
return ldap.LDAPResultSuccess, nil
}
+13
View File
@@ -0,0 +1,13 @@
// 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
type LDAPResultCode uint8
const (
LDAPBindAuthSimple = 0
LDAPBindAuthSASL = 3
)
+107
View File
@@ -0,0 +1,107 @@
package ldapserver
import (
"errors"
"fmt"
"net"
ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3"
)
func HandleModifyRequest(req *ber.Packet, boundDN string, server *Server, conn net.Conn) error {
if boundDN == "" {
return ldap.NewError(ldap.LDAPResultInsufficientAccessRights, errors.New("anonymous Write denied"))
}
modReq, err := parseModifyRequest(req)
if err != nil {
return err
}
logger.V(1).Info("Parsed Modification", "request", dumpModRequest(modReq))
fnNames := []string{}
for k := range server.ModifyFns {
fnNames = append(fnNames, k)
}
fn := routeFunc(modReq.DN, fnNames)
var modifier Modifier
if modifier = server.ModifyFns[fn]; modifier == nil {
if fn == "" {
err = fmt.Errorf("no suitable handler found for dn: '%s'", modReq.DN)
} else {
err = fmt.Errorf("handler '%s' does not support modify", fn)
}
return ldap.NewError(ldap.LDAPResultUnwillingToPerform, err)
}
code, err := modifier.Modify(boundDN, modReq, conn)
return ldap.NewError(uint16(code), err)
}
func dumpModRequest(mr *ldap.ModifyRequest) string {
str := fmt.Sprintf("dn: %s\n", mr.DN)
for _, change := range mr.Changes {
str += fmt.Sprintf("op: %d\n attr: %s values: %v\n", change.Operation, change.Modification.Type, change.Modification.Vals)
}
return str
}
func parseModifyRequest(req *ber.Packet) (*ldap.ModifyRequest, error) {
modReq := ldap.ModifyRequest{}
// LDAP Modify requests have 2 Elements (DN and AttributeList)
if len(req.Children) != 2 {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("invalid modify request"))
}
dn, ok := req.Children[0].Value.(string)
if !ok {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding entry DN"))
}
_, err := ldap.ParseDN(dn)
if err != nil {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, err)
}
modReq.DN = dn
ml, err := parseModList(req.Children[1])
if err != nil {
return nil, err
}
modReq.Changes = ml
return &modReq, nil
}
func parseModList(req *ber.Packet) ([]ldap.Change, error) {
var changes []ldap.Change
if req.ClassType != ber.ClassUniversal || req.TagType != ber.TypeConstructed || req.Tag != ber.TagSequence {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding Changes List"))
}
for _, c := range req.Children {
var change ldap.Change
switch c.Children[0].Data.Bytes()[0] {
default:
return nil, ldap.NewError(ldap.LDAPResultDecodingError, errors.New("invalid change operation"))
case ldap.AddAttribute:
change.Operation = ldap.AddAttribute
logger.V(1).Info("op=add")
case ldap.ReplaceAttribute:
change.Operation = ldap.ReplaceAttribute
logger.V(1).Info("op=replace")
case ldap.DeleteAttribute:
change.Operation = ldap.DeleteAttribute
logger.V(1).Info("op=delete")
}
attr, err := parseAttribute(c.Children[1], true)
if err != nil {
return nil, err
}
change.Modification = ldap.PartialAttribute{Type: attr.Type, Vals: attr.Vals}
changes = append(changes, change)
}
return changes, nil
}
+83
View File
@@ -0,0 +1,83 @@
package ldapserver
import (
"errors"
"fmt"
"net"
ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3"
)
func HandleModifyDNRequest(req *ber.Packet, boundDN string, server *Server, conn net.Conn) error {
if boundDN == "" {
return ldap.NewError(ldap.LDAPResultInsufficientAccessRights, errors.New("anonymous Write denied"))
}
modDNReq, err := parseModifyDNRequest(req)
if err != nil {
return err
}
fnNames := []string{}
for k := range server.ModifyDNFns {
fnNames = append(fnNames, k)
}
fn := routeFunc(modDNReq.DN, fnNames)
var rename Renamer
if rename = server.ModifyDNFns[fn]; rename == nil {
if fn == "" {
err = fmt.Errorf("no suitable handler found for dn: '%s'", modDNReq.DN)
} else {
err = fmt.Errorf("handler '%s' does not support rename", fn)
}
return ldap.NewError(ldap.LDAPResultUnwillingToPerform, err)
}
code, err := rename.ModifyDN(boundDN, modDNReq, conn)
return ldap.NewError(uint16(code), err)
return ldap.NewError(ldap.LDAPResultProtocolError, errors.New("invalid ModifyDN request"))
}
func parseModifyDNRequest(req *ber.Packet) (*ldap.ModifyDNRequest, error) {
modDNReq := ldap.ModifyDNRequest{}
// LDAP ModifyDN requests have up to 4 Elements (DN, newRDN, deleteOld flag and new superior)
if len(req.Children) < 3 || len(req.Children) > 4 {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("invalid ModifyDN request"))
}
dn, ok := req.Children[0].Value.(string)
if !ok {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding entry DN"))
}
_, err := ldap.ParseDN(dn)
if err != nil {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, err)
}
modDNReq.DN = dn
newRDN, ok := req.Children[1].Value.(string)
if !ok {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding entry new RDN"))
}
_, err = ldap.ParseDN(newRDN)
if err != nil {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, err)
}
modDNReq.NewRDN = newRDN
removeOld, ok := req.Children[2].Value.(bool)
if !ok {
return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding 'deleteOldRDN` flag"))
}
modDNReq.DeleteOldRDN = removeOld
// moving to a new subtree is not yet supported
if len(req.Children) == 4 {
return nil, ldap.NewError(ldap.LDAPResultUnwillingToPerform, errors.New("moving to 'newSuperior' is not implemented"))
}
return &modDNReq, nil
}
+119
View File
@@ -0,0 +1,119 @@
package ldapserver
import (
"errors"
"fmt"
"net"
ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3"
"github.com/libregraph/idm/pkg/ldappassword"
)
const pwmodOID = "1.3.6.1.4.1.4203.1.11.1"
const (
TagReqIdentity = 0
TagReqOldPW = 1
TagReqNewPW = 2
TagRespGenPW = 0
)
func init() {
RegisterExtendedOperation(pwmodOID, HandlePasswordModifyExOp)
}
func HandlePasswordModifyExOp(req *ber.Packet, boundDN string, server *Server, conn net.Conn) (*ber.Packet, error) {
var passwordGenerated bool
logger.V(1).Info("HandlePasswordModifyExOp")
if boundDN == "" {
return nil, ldap.NewError(ldap.LDAPResultUnwillingToPerform, errors.New("authentication required"))
}
pwReq, err := parsePasswordModifyExop(req)
if err != nil {
return nil, err
}
// If `UserIdentity` is empty, this is a request to update the bound user's own password
if pwReq.UserIdentity == "" {
pwReq.UserIdentity = boundDN
}
if pwReq.NewPassword == "" {
// New password empty means, we're requested to generate a new password
var err error
if pwReq.NewPassword, err = ldappassword.GenerateRandomPassword(server.GeneratedPasswordLength); err != nil {
logger.Error(err, "Failed to generate new password")
return nil, ldap.NewError(ldap.LDAPResultOperationsError, errors.New("Failed to generate new Password"))
}
passwordGenerated = true
}
pwReq.NewPassword, err = ldappassword.Hash(pwReq.NewPassword, "{ARGON2}")
if err != nil {
return nil, ldap.NewError(ldap.LDAPResultOperationsError, err)
}
logger.V(1).Info("Modify password extended operation", "dn", pwReq.UserIdentity)
fnNames := []string{}
for k := range server.PasswordExOpFns {
fnNames = append(fnNames, k)
}
fn := routeFunc(pwReq.UserIdentity, fnNames)
var pwUpdatefn PasswordUpdater
if pwUpdatefn = server.PasswordExOpFns[fn]; pwUpdatefn == nil {
if fn == "" {
err = fmt.Errorf("no suitable handler found for dn: '%s'", pwReq.UserIdentity)
} else {
err = fmt.Errorf("handler '%s' does not support add", fn)
}
return nil, ldap.NewError(ldap.LDAPResultUnwillingToPerform, err)
}
code, err := pwUpdatefn.ModifyPasswordExop(boundDN, pwReq, conn)
if code != ldap.LDAPResultSuccess {
return nil, ldap.NewError(uint16(code), err)
}
var response *ber.Packet
if passwordGenerated {
response = ber.NewSequence("PasswdModifyResponseValue")
response.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, pwReq.NewPassword, "genPasswd"))
}
return response, nil
}
func parsePasswordModifyExop(req *ber.Packet) (*ldap.PasswordModifyRequest, error) {
pwReq := ldap.PasswordModifyRequest{}
// An absent (or empty) body of the request is valid. Translates into: "generate a new password for
// for the current user"
if req == nil {
return &pwReq, nil
}
inner := ber.DecodePacket(req.Data.Bytes())
if inner == nil {
return &pwReq, nil
}
if len(inner.Children) > 3 {
return nil, ldap.NewError(ldap.LDAPResultDecodingError, errors.New("invalid request"))
}
for _, kid := range inner.Children {
if kid.ClassType != ber.ClassContext {
return nil, ldap.NewError(ldap.LDAPResultDecodingError, errors.New("invalid request"))
}
switch kid.Tag {
default:
return nil, ldap.NewError(ldap.LDAPResultDecodingError, errors.New("invalid request"))
case TagReqIdentity:
pwReq.UserIdentity = kid.Data.String()
case TagReqOldPW:
pwReq.OldPassword = kid.Data.String()
case TagReqNewPW:
pwReq.NewPassword = kid.Data.String()
}
}
return &pwReq, nil
}
+225
View File
@@ -0,0 +1,225 @@
// 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 (
"errors"
"net"
"strings"
ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3"
)
func HandleSearchRequest(req *ber.Packet, controls *[]ldap.Control, messageID int64, boundDN string, server *Server, conn net.Conn) (doneControls *[]ldap.Control, resultErr error) {
searchReq, err := parseSearchRequest(boundDN, req, controls)
if err != nil {
return nil, ldap.NewError(ldap.LDAPResultOperationsError, err)
}
var filterPacket *ber.Packet
if server.EnforceLDAP {
filterPacket, err = ldap.CompileFilter(searchReq.Filter)
if err != nil {
return nil, ldap.NewError(ldap.LDAPResultOperationsError, err)
}
}
fnNames := []string{}
for k := range server.SearchFns {
fnNames = append(fnNames, k)
}
fn := routeFunc(searchReq.BaseDN, fnNames)
searchResp, err := server.SearchFns[fn].Search(boundDN, searchReq, conn)
if err != nil {
return &searchResp.Controls, ldap.NewError(uint16(searchResp.ResultCode), err)
}
if server.EnforceLDAP {
if searchReq.DerefAliases != ldap.NeverDerefAliases { // [-a {never|always|search|find}
// TODO: Server DerefAliases not supported: RFC4511 4.5.1.3
}
if searchReq.TimeLimit > 0 {
// TODO: Server TimeLimit not implemented
}
}
i := 0
for _, entry := range searchResp.Entries {
if server.EnforceLDAP {
// filter
keep, resultCode := ServerApplyFilter(filterPacket, entry)
if resultCode != ldap.LDAPResultSuccess {
return &searchResp.Controls, ldap.NewError(uint16(resultCode), errors.New("ServerApplyFilter error"))
}
if !keep {
continue
}
keep, resultCode = ServerFilterScope(searchReq.BaseDN, searchReq.Scope, entry)
if resultCode != ldap.LDAPResultSuccess {
return &searchResp.Controls, ldap.NewError(uint16(resultCode), errors.New("ServerApplyScope error"))
}
if !keep {
continue
}
resultCode, err = ServerFilterAttributes(searchReq.Attributes, entry)
if err != nil {
return &searchResp.Controls, ldap.NewError(uint16(resultCode), err)
}
// size limit
if searchReq.SizeLimit > 0 && i >= searchReq.SizeLimit {
resultErr = ldap.NewError(
ldap.LDAPResultSizeLimitExceeded,
errors.New(ldap.LDAPResultCodeMap[ldap.LDAPResultSizeLimitExceeded]),
)
break
}
i++
}
// respond
responsePacket := encodeSearchResponse(messageID, searchReq, entry)
if err = sendPacket(conn, responsePacket); err != nil {
return &searchResp.Controls, ldap.NewError(ldap.LDAPResultOperationsError, err)
}
}
return &searchResp.Controls, resultErr
}
func parseSearchRequest(boundDN string, req *ber.Packet, controls *[]ldap.Control) (*ldap.SearchRequest, error) {
if len(req.Children) != 8 {
return &ldap.SearchRequest{}, ldap.NewError(ldap.LDAPResultOperationsError, errors.New("Bad search request"))
}
// Parse the request.
baseObject, ok := req.Children[0].Value.(string)
if !ok {
return &ldap.SearchRequest{}, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("Bad search request"))
}
s, ok := req.Children[1].Value.(int64)
if !ok {
return &ldap.SearchRequest{}, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("Bad search request"))
}
scope := int(s)
d, ok := req.Children[2].Value.(int64)
if !ok {
return &ldap.SearchRequest{}, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("Bad search request"))
}
derefAliases := int(d)
s, ok = req.Children[3].Value.(int64)
if !ok {
return &ldap.SearchRequest{}, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("Bad search request"))
}
sizeLimit := int(s)
t, ok := req.Children[4].Value.(int64)
if !ok {
return &ldap.SearchRequest{}, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("Bad search request"))
}
timeLimit := int(t)
typesOnly := false
if req.Children[5].Value != nil {
typesOnly, ok = req.Children[5].Value.(bool)
if !ok {
return &ldap.SearchRequest{}, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("Bad search request"))
}
}
filter, err := DecompileFilter(req.Children[6])
if err != nil {
return &ldap.SearchRequest{}, err
}
attributes := []string{}
for _, attr := range req.Children[7].Children {
a, ok := attr.Value.(string)
if !ok {
return &ldap.SearchRequest{}, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("Bad search request"))
}
attributes = append(attributes, a)
}
searchReq := &ldap.SearchRequest{
BaseDN: baseObject,
Scope: scope,
DerefAliases: derefAliases,
SizeLimit: sizeLimit,
TimeLimit: timeLimit,
TypesOnly: typesOnly,
Filter: filter,
Attributes: attributes,
Controls: *controls,
}
return searchReq, nil
}
func filterAttributes(entry *ldap.Entry, attributes []string) (*ldap.Entry, error) {
// Only return requested attributes.
newAttributes := []*ldap.EntryAttribute{}
for _, attr := range entry.Attributes {
for _, requested := range attributes {
if requested == "*" || strings.EqualFold(attr.Name, requested) {
newAttributes = append(newAttributes, attr)
}
}
}
entry.Attributes = newAttributes
return entry, nil
}
func encodeSearchResponse(messageID int64, req *ldap.SearchRequest, res *ldap.Entry) *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"))
searchEntry := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ldap.ApplicationSearchResultEntry, nil, "Search Result Entry")
searchEntry.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, res.DN, "Object Name"))
attrs := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes: ")
for _, attribute := range res.Attributes {
attrs.AppendChild(encodeSearchAttribute(attribute.Name, attribute.Values))
}
searchEntry.AppendChild(attrs)
responsePacket.AppendChild(searchEntry)
return responsePacket
}
func encodeSearchAttribute(name string, values []string) *ber.Packet {
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attribute")
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, name, "Attribute Name"))
valuesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "Attribute Values: ")
for _, value := range values {
valuesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Attribute Value"))
}
packet.AppendChild(valuesPacket)
return packet
}
func encodeSearchDone(messageID int64, ldapResultCode LDAPResultCode, doneControls *[]ldap.Control) *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"))
donePacket := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ldap.ApplicationSearchResultDone, nil, "Search result done")
donePacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(ldapResultCode), "resultCode: "))
donePacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "matchedDN: "))
donePacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "errorMessage: "))
responsePacket.AppendChild(donePacket)
if doneControls != nil {
contextPacket := ber.Encode(ber.ClassContext, ber.TypeConstructed, ber.TagEOC, nil, "Controls: ")
for _, control := range *doneControls {
contextPacket.AppendChild(control.Encode())
}
responsePacket.AppendChild(contextPacket)
}
// ber.PrintPacket(responsePacket)
return responsePacket
}
+491
View File
@@ -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
}
+119
View File
@@ -0,0 +1,119 @@
// Copyright 2012 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 (
"sync"
)
type Stats struct {
Conns uint64
ConnsCurrent uint64
ConnsMax uint64
Adds uint64
Binds uint64
Deletes uint64
ModifyDNs uint64
Modifies uint64
Unbinds uint64
Searches uint64
statsMutex sync.RWMutex
}
func (stats *Stats) countConns(delta uint64) {
if stats != nil {
stats.statsMutex.Lock()
stats.Conns += delta
stats.ConnsCurrent += delta
if stats.ConnsCurrent > stats.ConnsMax {
stats.ConnsMax = stats.ConnsCurrent
}
stats.statsMutex.Unlock()
}
}
func (stats *Stats) countConnsClose(delta uint64) {
if stats != nil {
stats.statsMutex.Lock()
stats.ConnsCurrent -= delta
stats.statsMutex.Unlock()
}
}
func (stats *Stats) countAdds(delta uint64) {
if stats != nil {
stats.statsMutex.Lock()
stats.Adds += delta
stats.statsMutex.Lock()
}
}
func (stats *Stats) countBinds(delta uint64) {
if stats != nil {
stats.statsMutex.Lock()
stats.Binds += delta
stats.statsMutex.Unlock()
}
}
func (stats *Stats) countDeletes(delta uint64) {
if stats != nil {
stats.statsMutex.Lock()
stats.Deletes += delta
stats.statsMutex.Unlock()
}
}
func (stats *Stats) countModifyDNs(delta uint64) {
if stats != nil {
stats.statsMutex.Lock()
stats.ModifyDNs += delta
stats.statsMutex.Unlock()
}
}
func (stats *Stats) countModifies(delta uint64) {
if stats != nil {
stats.statsMutex.Lock()
stats.Modifies += delta
stats.statsMutex.Unlock()
}
}
func (stats *Stats) countUnbinds(delta uint64) {
if stats != nil {
stats.statsMutex.Lock()
stats.Unbinds += delta
stats.statsMutex.Unlock()
}
}
func (stats *Stats) countSearches(delta uint64) {
if stats != nil {
stats.statsMutex.Lock()
stats.Searches += delta
stats.statsMutex.Unlock()
}
}
func (stats *Stats) Clone() *Stats {
var s2 *Stats
if stats != nil {
s2 = &Stats{}
stats.statsMutex.RLock()
s2.Conns = stats.Conns
s2.ConnsCurrent = stats.ConnsCurrent
s2.Adds = stats.Adds
s2.Binds = stats.Binds
s2.Deletes = stats.Deletes
s2.ModifyDNs = stats.ModifyDNs
s2.Modifies = stats.Modifies
s2.Unbinds = stats.Unbinds
s2.Searches = stats.Searches
stats.statsMutex.RUnlock()
}
return s2
}