switch back to upstream

Signed-off-by: Christian Richter <c.richter@opencloud.eu>
This commit is contained in:
Christian Richter
2025-04-28 13:43:21 +02:00
parent 1a79e1aa7a
commit 6c6033a830
16 changed files with 904 additions and 291 deletions
+94 -12
View File
@@ -3,15 +3,19 @@ package ldap
import (
"bytes"
"crypto/md5"
"encoding/binary"
"encoding/hex"
enchex "encoding/hex"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"strings"
"unicode/utf16"
"github.com/Azure/go-ntlmssp"
ber "github.com/go-asn1-ber/asn1-ber"
"golang.org/x/crypto/md4" //nolint:staticcheck
)
// SimpleBindRequest represents a username/password bind operation
@@ -216,7 +220,7 @@ func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*Diges
}
}
if params != nil {
if len(params) > 0 {
resp := computeResponse(
params,
"ldap/"+strings.ToLower(digestMD5BindRequest.Host),
@@ -249,6 +253,34 @@ func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*Diges
if err != nil {
return nil, fmt.Errorf("read packet: %s", err)
}
if len(packet.Children) == 2 {
response := packet.Children[1]
if response == nil {
return result, GetLDAPError(packet)
}
if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 {
if ber.Type(response.Children[0].Tag) == ber.Type(ber.TagInteger) || ber.Type(response.Children[0].Tag) == ber.Type(ber.TagEnumerated) {
resultCode := uint16(response.Children[0].Value.(int64))
if resultCode == 14 {
msgCtx, err := l.doRequest(digestMD5BindRequest)
if err != nil {
return nil, err
}
defer l.finishMessage(msgCtx)
packetResponse, ok := <-msgCtx.responses
if !ok {
return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
}
packet, err = packetResponse.ReadPacket()
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
if err != nil {
return nil, fmt.Errorf("read packet: %s", err)
}
}
}
}
}
}
err = GetLDAPError(packet)
@@ -406,17 +438,36 @@ type NTLMBindRequest struct {
Hash string
// Controls are optional controls to send with the bind request
Controls []Control
// Negotiator allows to specify a custom NTLM negotiator.
Negotiator NTLMNegotiator
}
func (req *NTLMBindRequest) appendTo(envelope *ber.Packet) error {
// NTLMNegotiator is an abstraction of an NTLM implementation that produces and
// processes NTLM binary tokens.
type NTLMNegotiator interface {
Negotiate(domain string, workstation string) ([]byte, error)
ChallengeResponse(challenge []byte, username string, hash string) ([]byte, error)
}
func (req *NTLMBindRequest) appendTo(envelope *ber.Packet) (err error) {
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
var negMessage []byte
// generate an NTLMSSP Negotiation message for the specified domain (it can be blank)
negMessage, err := ntlmssp.NewNegotiateMessage(req.Domain, "")
if err != nil {
return fmt.Errorf("err creating negmessage: %s", err)
switch {
case req.Negotiator == nil:
negMessage, err = ntlmssp.NewNegotiateMessage(req.Domain, "")
if err != nil {
return fmt.Errorf("create NTLM negotiate message: %s", err)
}
default:
negMessage, err = req.Negotiator.Negotiate(req.Domain, "")
if err != nil {
return fmt.Errorf("create NTLM negotiate message with custom negotiator: %s", err)
}
}
// append the generated NTLMSSP message as a TagEnumerated BER value
@@ -514,18 +565,29 @@ func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindRes
if ntlmsspChallenge != nil {
var err error
var responseMessage []byte
// generate a response message to the challenge with the given Username/Password if password is provided
if ntlmBindRequest.Hash != "" {
switch {
case ntlmBindRequest.Hash == "" && ntlmBindRequest.Password == "" && !ntlmBindRequest.AllowEmptyPassword:
err = fmt.Errorf("need a password or hash to generate reply")
case ntlmBindRequest.Negotiator == nil && ntlmBindRequest.Hash != "":
responseMessage, err = ntlmssp.ProcessChallengeWithHash(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Hash)
} else if ntlmBindRequest.Password != "" || ntlmBindRequest.AllowEmptyPassword {
case ntlmBindRequest.Negotiator == nil && (ntlmBindRequest.Password != "" || ntlmBindRequest.AllowEmptyPassword):
// generate a response message to the challenge with the given Username/Password if password is provided
_, _, domainNeeded := ntlmssp.GetDomain(ntlmBindRequest.Username)
responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password, domainNeeded)
} else {
err = fmt.Errorf("need a password or hash to generate reply")
default:
hash := ntlmBindRequest.Hash
if len(hash) == 0 {
hash = ntHash(ntlmBindRequest.Password)
}
responseMessage, err = ntlmBindRequest.Negotiator.ChallengeResponse(ntlmsspChallenge, ntlmBindRequest.Username, hash)
}
if err != nil {
return result, fmt.Errorf("parsing ntlm-challenge: %s", err)
return result, fmt.Errorf("process NTLM challenge: %s", err)
}
packet = ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
@@ -559,6 +621,18 @@ func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindRes
return result, err
}
func ntHash(pass string) string {
runes := utf16.Encode([]rune(pass))
b := bytes.Buffer{}
_ = binary.Write(&b, binary.LittleEndian, &runes)
hash := md4.New()
_, _ = hash.Write(b.Bytes())
return hex.EncodeToString(hash.Sum(nil))
}
// GSSAPIClient interface is used as the client-side implementation for the
// GSSAPI SASL mechanism.
// Interface inspired by GSSAPIClient from golang.org/x/crypto/ssh
@@ -577,6 +651,9 @@ type GSSAPIClient interface {
// to InitSecContext via the token parameters.
// See RFC 4752 section 3.1.
InitSecContext(target string, token []byte) (outputToken []byte, needContinue bool, err error)
// InitSecContextWithOptions is the same as InitSecContext but allows for additional options to be passed to the context establishment.
// See RFC 4752 section 3.1.
InitSecContextWithOptions(target string, token []byte, options []int) (outputToken []byte, needContinue bool, err error)
// NegotiateSaslAuth performs the last step of the Sasl handshake.
// It takes a token, which, when unwrapped, describes the servers supported
// security layers (first octet) and maximum receive buffer (remaining
@@ -614,6 +691,11 @@ func (l *Conn) GSSAPIBind(client GSSAPIClient, servicePrincipal, authzid string)
// GSSAPIBindRequest performs the GSSAPI SASL bind using the provided GSSAPI client.
func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) error {
return l.GSSAPIBindRequestWithAPOptions(client, req, []int{})
}
// GSSAPIBindRequest performs the GSSAPI SASL bind using the provided GSSAPI client.
func (l *Conn) GSSAPIBindRequestWithAPOptions(client GSSAPIClient, req *GSSAPIBindRequest, APOptions []int) error {
//nolint:errcheck
defer client.DeleteSecContext()
@@ -624,7 +706,7 @@ func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) er
for {
if needInit {
// Establish secure context between client and server.
reqToken, needInit, err = client.InitSecContext(req.ServicePrincipalName, recvToken)
reqToken, needInit, err = client.InitSecContextWithOptions(req.ServicePrincipalName, recvToken, APOptions)
if err != nil {
return err
}
+9 -6
View File
@@ -709,7 +709,7 @@ func (c *ControlDirSync) Encode() *ber.Packet {
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeDirSync, "Control Type ("+ControlTypeMap[ControlTypeDirSync]+")"))
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) // must be true always
packet.AppendChild(ber.NewLDAPBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) // must be true always
val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)")
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value")
@@ -755,7 +755,7 @@ func NewControlServerSideSorting(value *ber.Packet) (*ControlServerSideSorting,
sequences := val[0].Children
for i, sequence := range sequences {
sortKey := &SortKey{}
sortKey := new(SortKey)
if len(sequence.Children) < 2 {
return nil, fmt.Errorf("attributeType or matchingRule is missing from sequence %d", i)
@@ -864,10 +864,14 @@ func (c ControlServerSideSortingCode) Valid() error {
}
func NewControlServerSideSortingResult(pkt *ber.Packet) (*ControlServerSideSortingResult, error) {
control := &ControlServerSideSortingResult{}
control := new(ControlServerSideSortingResult)
if pkt == nil || len(pkt.Children) == 0 {
return nil, fmt.Errorf("bad packet")
// This is currently not compliant with the ServerSideSorting RFC (see https://datatracker.ietf.org/doc/html/rfc2891#section-1.2).
// but it's necessary because there seems to be a bug in the implementation of the popular OpenLDAP server.
//
// See: https://github.com/go-ldap/ldap/pull/546
return control, nil
}
codeInt, err := ber.ParseInt64(pkt.Children[0].Data.Bytes())
@@ -875,8 +879,7 @@ func NewControlServerSideSortingResult(pkt *ber.Packet) (*ControlServerSideSorti
return nil, err
}
code := ControlServerSideSortingCode(codeInt)
if err := code.Valid(); err != nil {
if err = ControlServerSideSortingCode(codeInt).Valid(); err != nil {
return nil, err
}