[Vendor] Update go-ldap to v3.2.4 (#13163)
* [Vendor] update go-ldap to v3.0.3 * update go-ldap to v3.2.4 Co-authored-by: techknowlogick <techknowlogick@gitea.io>mj-v1.14.3
parent
bcf45bb162
commit
e374bb7e2d
@ -0,0 +1,17 @@
|
|||||||
|
sudo: false
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- go get -u golang.org/x/lint/golint
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.10.x
|
||||||
|
- master
|
||||||
|
|
||||||
|
script:
|
||||||
|
- test -z "$(gofmt -s -l . | tee /dev/stderr)"
|
||||||
|
- test -z "$(golint ./... | tee /dev/stderr)"
|
||||||
|
- go vet ./...
|
||||||
|
- go build -v ./...
|
||||||
|
- go test -v ./...
|
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Microsoft
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,29 @@
|
|||||||
|
# go-ntlmssp
|
||||||
|
Golang package that provides NTLM/Negotiate authentication over HTTP
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/Azure/go-ntlmssp?status.svg)](https://godoc.org/github.com/Azure/go-ntlmssp) [![Build Status](https://travis-ci.org/Azure/go-ntlmssp.svg?branch=dev)](https://travis-ci.org/Azure/go-ntlmssp)
|
||||||
|
|
||||||
|
Protocol details from https://msdn.microsoft.com/en-us/library/cc236621.aspx
|
||||||
|
Implementation hints from http://davenport.sourceforge.net/ntlm.html
|
||||||
|
|
||||||
|
This package only implements authentication, no key exchange or encryption. It
|
||||||
|
only supports Unicode (UTF16LE) encoding of protocol strings, no OEM encoding.
|
||||||
|
This package implements NTLMv2.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
url, user, password := "http://www.example.com/secrets", "robpike", "pw123"
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: ntlmssp.Negotiator{
|
||||||
|
RoundTripper:&http.Transport{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
req.SetBasicAuth(user, password)
|
||||||
|
res, _ := client.Do(req)
|
||||||
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
@ -0,0 +1,183 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type authenicateMessage struct {
|
||||||
|
LmChallengeResponse []byte
|
||||||
|
NtChallengeResponse []byte
|
||||||
|
|
||||||
|
TargetName string
|
||||||
|
UserName string
|
||||||
|
|
||||||
|
// only set if negotiateFlag_NTLMSSP_NEGOTIATE_KEY_EXCH
|
||||||
|
EncryptedRandomSessionKey []byte
|
||||||
|
|
||||||
|
NegotiateFlags negotiateFlags
|
||||||
|
|
||||||
|
MIC []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type authenticateMessageFields struct {
|
||||||
|
messageHeader
|
||||||
|
LmChallengeResponse varField
|
||||||
|
NtChallengeResponse varField
|
||||||
|
TargetName varField
|
||||||
|
UserName varField
|
||||||
|
Workstation varField
|
||||||
|
_ [8]byte
|
||||||
|
NegotiateFlags negotiateFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m authenicateMessage) MarshalBinary() ([]byte, error) {
|
||||||
|
if !m.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEUNICODE) {
|
||||||
|
return nil, errors.New("Only unicode is supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
target, user := toUnicode(m.TargetName), toUnicode(m.UserName)
|
||||||
|
workstation := toUnicode("go-ntlmssp")
|
||||||
|
|
||||||
|
ptr := binary.Size(&authenticateMessageFields{})
|
||||||
|
f := authenticateMessageFields{
|
||||||
|
messageHeader: newMessageHeader(3),
|
||||||
|
NegotiateFlags: m.NegotiateFlags,
|
||||||
|
LmChallengeResponse: newVarField(&ptr, len(m.LmChallengeResponse)),
|
||||||
|
NtChallengeResponse: newVarField(&ptr, len(m.NtChallengeResponse)),
|
||||||
|
TargetName: newVarField(&ptr, len(target)),
|
||||||
|
UserName: newVarField(&ptr, len(user)),
|
||||||
|
Workstation: newVarField(&ptr, len(workstation)),
|
||||||
|
}
|
||||||
|
|
||||||
|
f.NegotiateFlags.Unset(negotiateFlagNTLMSSPNEGOTIATEVERSION)
|
||||||
|
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &f); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &m.LmChallengeResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &m.NtChallengeResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &target); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &user); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &workstation); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//ProcessChallenge crafts an AUTHENTICATE message in response to the CHALLENGE message
|
||||||
|
//that was received from the server
|
||||||
|
func ProcessChallenge(challengeMessageData []byte, user, password string) ([]byte, error) {
|
||||||
|
if user == "" && password == "" {
|
||||||
|
return nil, errors.New("Anonymous authentication not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cm challengeMessage
|
||||||
|
if err := cm.UnmarshalBinary(challengeMessageData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATELMKEY) {
|
||||||
|
return nil, errors.New("Only NTLM v2 is supported, but server requested v1 (NTLMSSP_NEGOTIATE_LM_KEY)")
|
||||||
|
}
|
||||||
|
if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEKEYEXCH) {
|
||||||
|
return nil, errors.New("Key exchange requested but not supported (NTLMSSP_NEGOTIATE_KEY_EXCH)")
|
||||||
|
}
|
||||||
|
|
||||||
|
am := authenicateMessage{
|
||||||
|
UserName: user,
|
||||||
|
TargetName: cm.TargetName,
|
||||||
|
NegotiateFlags: cm.NegotiateFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := cm.TargetInfo[avIDMsvAvTimestamp]
|
||||||
|
if timestamp == nil { // no time sent, take current time
|
||||||
|
ft := uint64(time.Now().UnixNano()) / 100
|
||||||
|
ft += 116444736000000000 // add time between unix & windows offset
|
||||||
|
timestamp = make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(timestamp, ft)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientChallenge := make([]byte, 8)
|
||||||
|
rand.Reader.Read(clientChallenge)
|
||||||
|
|
||||||
|
ntlmV2Hash := getNtlmV2Hash(password, user, cm.TargetName)
|
||||||
|
|
||||||
|
am.NtChallengeResponse = computeNtlmV2Response(ntlmV2Hash,
|
||||||
|
cm.ServerChallenge[:], clientChallenge, timestamp, cm.TargetInfoRaw)
|
||||||
|
|
||||||
|
if cm.TargetInfoRaw == nil {
|
||||||
|
am.LmChallengeResponse = computeLmV2Response(ntlmV2Hash,
|
||||||
|
cm.ServerChallenge[:], clientChallenge)
|
||||||
|
}
|
||||||
|
return am.MarshalBinary()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessChallengeWithHash(challengeMessageData []byte, user, hash string) ([]byte, error) {
|
||||||
|
if user == "" && hash == "" {
|
||||||
|
return nil, errors.New("Anonymous authentication not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cm challengeMessage
|
||||||
|
if err := cm.UnmarshalBinary(challengeMessageData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATELMKEY) {
|
||||||
|
return nil, errors.New("Only NTLM v2 is supported, but server requested v1 (NTLMSSP_NEGOTIATE_LM_KEY)")
|
||||||
|
}
|
||||||
|
if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEKEYEXCH) {
|
||||||
|
return nil, errors.New("Key exchange requested but not supported (NTLMSSP_NEGOTIATE_KEY_EXCH)")
|
||||||
|
}
|
||||||
|
|
||||||
|
am := authenicateMessage{
|
||||||
|
UserName: user,
|
||||||
|
TargetName: cm.TargetName,
|
||||||
|
NegotiateFlags: cm.NegotiateFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := cm.TargetInfo[avIDMsvAvTimestamp]
|
||||||
|
if timestamp == nil { // no time sent, take current time
|
||||||
|
ft := uint64(time.Now().UnixNano()) / 100
|
||||||
|
ft += 116444736000000000 // add time between unix & windows offset
|
||||||
|
timestamp = make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(timestamp, ft)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientChallenge := make([]byte, 8)
|
||||||
|
rand.Reader.Read(clientChallenge)
|
||||||
|
|
||||||
|
hashParts := strings.Split(hash, ":")
|
||||||
|
if len(hashParts) > 1 {
|
||||||
|
hash = hashParts[1]
|
||||||
|
}
|
||||||
|
hashBytes, err := hex.DecodeString(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ntlmV2Hash := hmacMd5(hashBytes, toUnicode(strings.ToUpper(user)+cm.TargetName))
|
||||||
|
|
||||||
|
am.NtChallengeResponse = computeNtlmV2Response(ntlmV2Hash,
|
||||||
|
cm.ServerChallenge[:], clientChallenge, timestamp, cm.TargetInfoRaw)
|
||||||
|
|
||||||
|
if cm.TargetInfoRaw == nil {
|
||||||
|
am.LmChallengeResponse = computeLmV2Response(ntlmV2Hash,
|
||||||
|
cm.ServerChallenge[:], clientChallenge)
|
||||||
|
}
|
||||||
|
return am.MarshalBinary()
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type authheader string
|
||||||
|
|
||||||
|
func (h authheader) IsBasic() bool {
|
||||||
|
return strings.HasPrefix(string(h), "Basic ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h authheader) IsNegotiate() bool {
|
||||||
|
return strings.HasPrefix(string(h), "Negotiate")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h authheader) IsNTLM() bool {
|
||||||
|
return strings.HasPrefix(string(h), "NTLM")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h authheader) GetData() ([]byte, error) {
|
||||||
|
p := strings.Split(string(h), " ")
|
||||||
|
if len(p) < 2 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.DecodeString(string(p[1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h authheader) GetBasicCreds() (username, password string, err error) {
|
||||||
|
d, err := h.GetData()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(string(d), ":", 2)
|
||||||
|
return parts[0], parts[1], nil
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
type avID uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
avIDMsvAvEOL avID = iota
|
||||||
|
avIDMsvAvNbComputerName
|
||||||
|
avIDMsvAvNbDomainName
|
||||||
|
avIDMsvAvDNSComputerName
|
||||||
|
avIDMsvAvDNSDomainName
|
||||||
|
avIDMsvAvDNSTreeName
|
||||||
|
avIDMsvAvFlags
|
||||||
|
avIDMsvAvTimestamp
|
||||||
|
avIDMsvAvSingleHost
|
||||||
|
avIDMsvAvTargetName
|
||||||
|
avIDMsvChannelBindings
|
||||||
|
)
|
@ -0,0 +1,82 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type challengeMessageFields struct {
|
||||||
|
messageHeader
|
||||||
|
TargetName varField
|
||||||
|
NegotiateFlags negotiateFlags
|
||||||
|
ServerChallenge [8]byte
|
||||||
|
_ [8]byte
|
||||||
|
TargetInfo varField
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m challengeMessageFields) IsValid() bool {
|
||||||
|
return m.messageHeader.IsValid() && m.MessageType == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
type challengeMessage struct {
|
||||||
|
challengeMessageFields
|
||||||
|
TargetName string
|
||||||
|
TargetInfo map[avID][]byte
|
||||||
|
TargetInfoRaw []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *challengeMessage) UnmarshalBinary(data []byte) error {
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
err := binary.Read(r, binary.LittleEndian, &m.challengeMessageFields)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !m.challengeMessageFields.IsValid() {
|
||||||
|
return fmt.Errorf("Message is not a valid challenge message: %+v", m.challengeMessageFields.messageHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.challengeMessageFields.TargetName.Len > 0 {
|
||||||
|
m.TargetName, err = m.challengeMessageFields.TargetName.ReadStringFrom(data, m.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEUNICODE))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.challengeMessageFields.TargetInfo.Len > 0 {
|
||||||
|
d, err := m.challengeMessageFields.TargetInfo.ReadFrom(data)
|
||||||
|
m.TargetInfoRaw = d
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.TargetInfo = make(map[avID][]byte)
|
||||||
|
r := bytes.NewReader(d)
|
||||||
|
for {
|
||||||
|
var id avID
|
||||||
|
var l uint16
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if id == avIDMsvAvEOL {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &l)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value := make([]byte, l)
|
||||||
|
n, err := r.Read(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != int(l) {
|
||||||
|
return fmt.Errorf("Expected to read %d bytes, got only %d", l, n)
|
||||||
|
}
|
||||||
|
m.TargetInfo[id] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
var signature = [8]byte{'N', 'T', 'L', 'M', 'S', 'S', 'P', 0}
|
||||||
|
|
||||||
|
type messageHeader struct {
|
||||||
|
Signature [8]byte
|
||||||
|
MessageType uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h messageHeader) IsValid() bool {
|
||||||
|
return bytes.Equal(h.Signature[:], signature[:]) &&
|
||||||
|
h.MessageType > 0 && h.MessageType < 4
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMessageHeader(messageType uint32) messageHeader {
|
||||||
|
return messageHeader{signature, messageType}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
type negotiateFlags uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
/*A*/ negotiateFlagNTLMSSPNEGOTIATEUNICODE negotiateFlags = 1 << 0
|
||||||
|
/*B*/ negotiateFlagNTLMNEGOTIATEOEM = 1 << 1
|
||||||
|
/*C*/ negotiateFlagNTLMSSPREQUESTTARGET = 1 << 2
|
||||||
|
|
||||||
|
/*D*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATESIGN = 1 << 4
|
||||||
|
/*E*/ negotiateFlagNTLMSSPNEGOTIATESEAL = 1 << 5
|
||||||
|
/*F*/ negotiateFlagNTLMSSPNEGOTIATEDATAGRAM = 1 << 6
|
||||||
|
/*G*/ negotiateFlagNTLMSSPNEGOTIATELMKEY = 1 << 7
|
||||||
|
|
||||||
|
/*H*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATENTLM = 1 << 9
|
||||||
|
|
||||||
|
/*J*/
|
||||||
|
negotiateFlagANONYMOUS = 1 << 11
|
||||||
|
/*K*/ negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED = 1 << 12
|
||||||
|
/*L*/ negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED = 1 << 13
|
||||||
|
|
||||||
|
/*M*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATEALWAYSSIGN = 1 << 15
|
||||||
|
/*N*/ negotiateFlagNTLMSSPTARGETTYPEDOMAIN = 1 << 16
|
||||||
|
/*O*/ negotiateFlagNTLMSSPTARGETTYPESERVER = 1 << 17
|
||||||
|
|
||||||
|
/*P*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY = 1 << 19
|
||||||
|
/*Q*/ negotiateFlagNTLMSSPNEGOTIATEIDENTIFY = 1 << 20
|
||||||
|
|
||||||
|
/*R*/
|
||||||
|
negotiateFlagNTLMSSPREQUESTNONNTSESSIONKEY = 1 << 22
|
||||||
|
/*S*/ negotiateFlagNTLMSSPNEGOTIATETARGETINFO = 1 << 23
|
||||||
|
|
||||||
|
/*T*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATEVERSION = 1 << 25
|
||||||
|
|
||||||
|
/*U*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATE128 = 1 << 29
|
||||||
|
/*V*/ negotiateFlagNTLMSSPNEGOTIATEKEYEXCH = 1 << 30
|
||||||
|
/*W*/ negotiateFlagNTLMSSPNEGOTIATE56 = 1 << 31
|
||||||
|
)
|
||||||
|
|
||||||
|
func (field negotiateFlags) Has(flags negotiateFlags) bool {
|
||||||
|
return field&flags == flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (field *negotiateFlags) Unset(flags negotiateFlags) {
|
||||||
|
*field = *field ^ (*field & flags)
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const expMsgBodyLen = 40
|
||||||
|
|
||||||
|
type negotiateMessageFields struct {
|
||||||
|
messageHeader
|
||||||
|
NegotiateFlags negotiateFlags
|
||||||
|
|
||||||
|
Domain varField
|
||||||
|
Workstation varField
|
||||||
|
|
||||||
|
Version
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultFlags = negotiateFlagNTLMSSPNEGOTIATETARGETINFO |
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATE56 |
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATE128 |
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATEUNICODE |
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY
|
||||||
|
|
||||||
|
//NewNegotiateMessage creates a new NEGOTIATE message with the
|
||||||
|
//flags that this package supports.
|
||||||
|
func NewNegotiateMessage(domainName, workstationName string) ([]byte, error) {
|
||||||
|
payloadOffset := expMsgBodyLen
|
||||||
|
flags := defaultFlags
|
||||||
|
|
||||||
|
if domainName != "" {
|
||||||
|
flags |= negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED
|
||||||
|
}
|
||||||
|
|
||||||
|
if workstationName != "" {
|
||||||
|
flags |= negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := negotiateMessageFields{
|
||||||
|
messageHeader: newMessageHeader(1),
|
||||||
|
NegotiateFlags: flags,
|
||||||
|
Domain: newVarField(&payloadOffset, len(domainName)),
|
||||||
|
Workstation: newVarField(&payloadOffset, len(workstationName)),
|
||||||
|
Version: DefaultVersion(),
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &msg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if b.Len() != expMsgBodyLen {
|
||||||
|
return nil, errors.New("incorrect body length")
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := strings.ToUpper(domainName + workstationName)
|
||||||
|
if _, err := b.WriteString(payload); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetDomain : parse domain name from based on slashes in the input
|
||||||
|
func GetDomain(user string) (string, string) {
|
||||||
|
domain := ""
|
||||||
|
|
||||||
|
if strings.Contains(user, "\\") {
|
||||||
|
ucomponents := strings.SplitN(user, "\\", 2)
|
||||||
|
domain = ucomponents[0]
|
||||||
|
user = ucomponents[1]
|
||||||
|
}
|
||||||
|
return user, domain
|
||||||
|
}
|
||||||
|
|
||||||
|
//Negotiator is a http.Roundtripper decorator that automatically
|
||||||
|
//converts basic authentication to NTLM/Negotiate authentication when appropriate.
|
||||||
|
type Negotiator struct{ http.RoundTripper }
|
||||||
|
|
||||||
|
//RoundTrip sends the request to the server, handling any authentication
|
||||||
|
//re-sends as needed.
|
||||||
|
func (l Negotiator) RoundTrip(req *http.Request) (res *http.Response, err error) {
|
||||||
|
// Use default round tripper if not provided
|
||||||
|
rt := l.RoundTripper
|
||||||
|
if rt == nil {
|
||||||
|
rt = http.DefaultTransport
|
||||||
|
}
|
||||||
|
// If it is not basic auth, just round trip the request as usual
|
||||||
|
reqauth := authheader(req.Header.Get("Authorization"))
|
||||||
|
if !reqauth.IsBasic() {
|
||||||
|
return rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
// Save request body
|
||||||
|
body := bytes.Buffer{}
|
||||||
|
if req.Body != nil {
|
||||||
|
_, err = body.ReadFrom(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Body.Close()
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
|
||||||
|
}
|
||||||
|
// first try anonymous, in case the server still finds us
|
||||||
|
// authenticated from previous traffic
|
||||||
|
req.Header.Del("Authorization")
|
||||||
|
res, err = rt.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusUnauthorized {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resauth := authheader(res.Header.Get("Www-Authenticate"))
|
||||||
|
if !resauth.IsNegotiate() && !resauth.IsNTLM() {
|
||||||
|
// Unauthorized, Negotiate not requested, let's try with basic auth
|
||||||
|
req.Header.Set("Authorization", string(reqauth))
|
||||||
|
io.Copy(ioutil.Discard, res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
|
||||||
|
|
||||||
|
res, err = rt.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusUnauthorized {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
resauth = authheader(res.Header.Get("Www-Authenticate"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if resauth.IsNegotiate() || resauth.IsNTLM() {
|
||||||
|
// 401 with request:Basic and response:Negotiate
|
||||||
|
io.Copy(ioutil.Discard, res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
// recycle credentials
|
||||||
|
u, p, err := reqauth.GetBasicCreds()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get domain from username
|
||||||
|
domain := ""
|
||||||
|
u, domain = GetDomain(u)
|
||||||
|
|
||||||
|
// send negotiate
|
||||||
|
negotiateMessage, err := NewNegotiateMessage(domain, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resauth.IsNTLM() {
|
||||||
|
req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(negotiateMessage))
|
||||||
|
} else {
|
||||||
|
req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(negotiateMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
|
||||||
|
|
||||||
|
res, err = rt.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// receive challenge?
|
||||||
|
resauth = authheader(res.Header.Get("Www-Authenticate"))
|
||||||
|
challengeMessage, err := resauth.GetData()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !(resauth.IsNegotiate() || resauth.IsNTLM()) || len(challengeMessage) == 0 {
|
||||||
|
// Negotiation failed, let client deal with response
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
io.Copy(ioutil.Discard, res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
// send authenticate
|
||||||
|
authenticateMessage, err := ProcessChallenge(challengeMessage, u, p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resauth.IsNTLM() {
|
||||||
|
req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(authenticateMessage))
|
||||||
|
} else {
|
||||||
|
req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(authenticateMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
|
||||||
|
|
||||||
|
return rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
// Package ntlmssp provides NTLM/Negotiate authentication over HTTP
|
||||||
|
//
|
||||||
|
// Protocol details from https://msdn.microsoft.com/en-us/library/cc236621.aspx,
|
||||||
|
// implementation hints from http://davenport.sourceforge.net/ntlm.html .
|
||||||
|
// This package only implements authentication, no key exchange or encryption. It
|
||||||
|
// only supports Unicode (UTF16LE) encoding of protocol strings, no OEM encoding.
|
||||||
|
// This package implements NTLMv2.
|
||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"golang.org/x/crypto/md4"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getNtlmV2Hash(password, username, target string) []byte {
|
||||||
|
return hmacMd5(getNtlmHash(password), toUnicode(strings.ToUpper(username)+target))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNtlmHash(password string) []byte {
|
||||||
|
hash := md4.New()
|
||||||
|
hash.Write(toUnicode(password))
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeNtlmV2Response(ntlmV2Hash, serverChallenge, clientChallenge,
|
||||||
|
timestamp, targetInfo []byte) []byte {
|
||||||
|
|
||||||
|
temp := []byte{1, 1, 0, 0, 0, 0, 0, 0}
|
||||||
|
temp = append(temp, timestamp...)
|
||||||
|
temp = append(temp, clientChallenge...)
|
||||||
|
temp = append(temp, 0, 0, 0, 0)
|
||||||
|
temp = append(temp, targetInfo...)
|
||||||
|
temp = append(temp, 0, 0, 0, 0)
|
||||||
|
|
||||||
|
NTProofStr := hmacMd5(ntlmV2Hash, serverChallenge, temp)
|
||||||
|
return append(NTProofStr, temp...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeLmV2Response(ntlmV2Hash, serverChallenge, clientChallenge []byte) []byte {
|
||||||
|
return append(hmacMd5(ntlmV2Hash, serverChallenge, clientChallenge), clientChallenge...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hmacMd5(key []byte, data ...[]byte) []byte {
|
||||||
|
mac := hmac.New(md5.New, key)
|
||||||
|
for _, d := range data {
|
||||||
|
mac.Write(d)
|
||||||
|
}
|
||||||
|
return mac.Sum(nil)
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
// helper func's for dealing with Windows Unicode (UTF16LE)
|
||||||
|
|
||||||
|
func fromUnicode(d []byte) (string, error) {
|
||||||
|
if len(d)%2 > 0 {
|
||||||
|
return "", errors.New("Unicode (UTF 16 LE) specified, but uneven data length")
|
||||||
|
}
|
||||||
|
s := make([]uint16, len(d)/2)
|
||||||
|
err := binary.Read(bytes.NewReader(d), binary.LittleEndian, &s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(s)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUnicode(s string) []byte {
|
||||||
|
uints := utf16.Encode([]rune(s))
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
binary.Write(&b, binary.LittleEndian, &uints)
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type varField struct {
|
||||||
|
Len uint16
|
||||||
|
MaxLen uint16
|
||||||
|
BufferOffset uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f varField) ReadFrom(buffer []byte) ([]byte, error) {
|
||||||
|
if len(buffer) < int(f.BufferOffset+uint32(f.Len)) {
|
||||||
|
return nil, errors.New("Error reading data, varField extends beyond buffer")
|
||||||
|
}
|
||||||
|
return buffer[f.BufferOffset : f.BufferOffset+uint32(f.Len)], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f varField) ReadStringFrom(buffer []byte, unicode bool) (string, error) {
|
||||||
|
d, err := f.ReadFrom(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if unicode { // UTF-16LE encoding scheme
|
||||||
|
return fromUnicode(d)
|
||||||
|
}
|
||||||
|
// OEM encoding, close enough to ASCII, since no code page is specified
|
||||||
|
return string(d), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVarField(ptr *int, fieldsize int) varField {
|
||||||
|
f := varField{
|
||||||
|
Len: uint16(fieldsize),
|
||||||
|
MaxLen: uint16(fieldsize),
|
||||||
|
BufferOffset: uint32(*ptr),
|
||||||
|
}
|
||||||
|
*ptr += fieldsize
|
||||||
|
return f
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
// Version is a struct representing https://msdn.microsoft.com/en-us/library/cc236654.aspx
|
||||||
|
type Version struct {
|
||||||
|
ProductMajorVersion uint8
|
||||||
|
ProductMinorVersion uint8
|
||||||
|
ProductBuild uint16
|
||||||
|
_ [3]byte
|
||||||
|
NTLMRevisionCurrent uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultVersion returns a Version with "sensible" defaults (Windows 7)
|
||||||
|
func DefaultVersion() Version {
|
||||||
|
return Version{
|
||||||
|
ProductMajorVersion: 6,
|
||||||
|
ProductMinorVersion: 1,
|
||||||
|
ProductBuild: 7601,
|
||||||
|
NTLMRevisionCurrent: 15,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.2.x
|
||||||
|
- 1.6.x
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- 1.14.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
|
||||||
|
arch:
|
||||||
|
- amd64
|
||||||
|
|
||||||
|
dist: xenial
|
||||||
|
|
||||||
|
env:
|
||||||
|
- GOARCH=amd64
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- os: windows
|
||||||
|
go: 1.14.x
|
||||||
|
- os: osx
|
||||||
|
go: 1.14.x
|
||||||
|
- os: linux
|
||||||
|
go: 1.14.x
|
||||||
|
arch: arm64
|
||||||
|
- os: linux
|
||||||
|
go: 1.14.x
|
||||||
|
env:
|
||||||
|
- GOARCH=386
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -cover ./... || go test -v ./...
|
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
|
||||||
|
Portions copyright (c) 2015-2016 go-asn1-ber Authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
224
vendor/gopkg.in/asn1-ber.v1/ber.go → vendor/github.com/go-asn1-ber/asn1-ber/ber.go
generated
vendored
224
vendor/gopkg.in/asn1-ber.v1/ber.go → vendor/github.com/go-asn1-ber/asn1-ber/ber.go
generated
vendored
@ -0,0 +1,105 @@
|
|||||||
|
package ber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidTimeFormat is returned when the generalizedTime string was not correct.
|
||||||
|
var ErrInvalidTimeFormat = errors.New("invalid time format")
|
||||||
|
|
||||||
|
var zeroTime = time.Time{}
|
||||||
|
|
||||||
|
// ParseGeneralizedTime parses a string value and if it conforms to
|
||||||
|
// GeneralizedTime[^0] format, will return a time.Time for that value.
|
||||||
|
//
|
||||||
|
// [^0]: https://www.itu.int/rec/T-REC-X.690-201508-I/en Section 11.7
|
||||||
|
func ParseGeneralizedTime(v []byte) (time.Time, error) {
|
||||||
|
var format string
|
||||||
|
var fract time.Duration
|
||||||
|
|
||||||
|
str := []byte(DecodeString(v))
|
||||||
|
tzIndex := bytes.IndexAny(str, "Z+-")
|
||||||
|
if tzIndex < 0 {
|
||||||
|
return zeroTime, ErrInvalidTimeFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
dot := bytes.IndexAny(str, ".,")
|
||||||
|
switch dot {
|
||||||
|
case -1:
|
||||||
|
switch tzIndex {
|
||||||
|
case 10:
|
||||||
|
format = `2006010215Z`
|
||||||
|
case 12:
|
||||||
|
format = `200601021504Z`
|
||||||
|
case 14:
|
||||||
|
format = `20060102150405Z`
|
||||||
|
default:
|
||||||
|
return zeroTime, ErrInvalidTimeFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
case 10, 12:
|
||||||
|
if tzIndex < dot {
|
||||||
|
return zeroTime, ErrInvalidTimeFormat
|
||||||
|
}
|
||||||
|
// a "," is also allowed, but would not be parsed by time.Parse():
|
||||||
|
str[dot] = '.'
|
||||||
|
|
||||||
|
// If <minute> is omitted, then <fraction> represents a fraction of an
|
||||||
|
// hour; otherwise, if <second> and <leap-second> are omitted, then
|
||||||
|
// <fraction> represents a fraction of a minute; otherwise, <fraction>
|
||||||
|
// represents a fraction of a second.
|
||||||
|
|
||||||
|
// parse as float from dot to timezone
|
||||||
|
f, err := strconv.ParseFloat(string(str[dot:tzIndex]), 64)
|
||||||
|
if err != nil {
|
||||||
|
return zeroTime, fmt.Errorf("failed to parse float: %s", err)
|
||||||
|
}
|
||||||
|
// ...and strip that part
|
||||||
|
str = append(str[:dot], str[tzIndex:]...)
|
||||||
|
tzIndex = dot
|
||||||
|
|
||||||
|
if dot == 10 {
|
||||||
|
fract = time.Duration(int64(f * float64(time.Hour)))
|
||||||
|
format = `2006010215Z`
|
||||||
|
} else {
|
||||||
|
fract = time.Duration(int64(f * float64(time.Minute)))
|
||||||
|
format = `200601021504Z`
|
||||||
|
}
|
||||||
|
|
||||||
|
case 14:
|
||||||
|
if tzIndex < dot {
|
||||||
|
return zeroTime, ErrInvalidTimeFormat
|
||||||
|
}
|
||||||
|
str[dot] = '.'
|
||||||
|
// no need for fractional seconds, time.Parse() handles that
|
||||||
|
format = `20060102150405Z`
|
||||||
|
|
||||||
|
default:
|
||||||
|
return zeroTime, ErrInvalidTimeFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
l := len(str)
|
||||||
|
switch l - tzIndex {
|
||||||
|
case 1:
|
||||||
|
if str[l-1] != 'Z' {
|
||||||
|
return zeroTime, ErrInvalidTimeFormat
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
format += `0700`
|
||||||
|
str = append(str, []byte("00")...)
|
||||||
|
case 5:
|
||||||
|
format += `0700`
|
||||||
|
default:
|
||||||
|
return zeroTime, ErrInvalidTimeFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.Parse(format, string(str))
|
||||||
|
if err != nil {
|
||||||
|
return zeroTime, fmt.Errorf("%s: %s", ErrInvalidTimeFormat, err)
|
||||||
|
}
|
||||||
|
return t.Add(fract), nil
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
module github.com/go-asn1-ber/asn1-ber
|
||||||
|
|
||||||
|
go 1.13
|
25
vendor/gopkg.in/asn1-ber.v1/header.go → vendor/github.com/go-asn1-ber/asn1-ber/header.go
generated
vendored
25
vendor/gopkg.in/asn1-ber.v1/header.go → vendor/github.com/go-asn1-ber/asn1-ber/header.go
generated
vendored
26
vendor/gopkg.in/asn1-ber.v1/length.go → vendor/github.com/go-asn1-ber/asn1-ber/length.go
generated
vendored
26
vendor/gopkg.in/asn1-ber.v1/length.go → vendor/github.com/go-asn1-ber/asn1-ber/length.go
generated
vendored
@ -0,0 +1,157 @@
|
|||||||
|
package ber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func encodeFloat(v float64) []byte {
|
||||||
|
switch {
|
||||||
|
case math.IsInf(v, 1):
|
||||||
|
return []byte{0x40}
|
||||||
|
case math.IsInf(v, -1):
|
||||||
|
return []byte{0x41}
|
||||||
|
case math.IsNaN(v):
|
||||||
|
return []byte{0x42}
|
||||||
|
case v == 0.0:
|
||||||
|
if math.Signbit(v) {
|
||||||
|
return []byte{0x43}
|
||||||
|
}
|
||||||
|
return []byte{}
|
||||||
|
default:
|
||||||
|
// we take the easy part ;-)
|
||||||
|
value := []byte(strconv.FormatFloat(v, 'G', -1, 64))
|
||||||
|
var ret []byte
|
||||||
|
if bytes.Contains(value, []byte{'E'}) {
|
||||||
|
ret = []byte{0x03}
|
||||||
|
} else {
|
||||||
|
ret = []byte{0x02}
|
||||||
|
}
|
||||||
|
ret = append(ret, value...)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseReal(v []byte) (val float64, err error) {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return 0.0, nil
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case v[0]&0x80 == 0x80:
|
||||||
|
val, err = parseBinaryFloat(v)
|
||||||
|
case v[0]&0xC0 == 0x40:
|
||||||
|
val, err = parseSpecialFloat(v)
|
||||||
|
case v[0]&0xC0 == 0x0:
|
||||||
|
val, err = parseDecimalFloat(v)
|
||||||
|
default:
|
||||||
|
return 0.0, fmt.Errorf("invalid info block")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0.0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if val == 0.0 && !math.Signbit(val) {
|
||||||
|
return 0.0, errors.New("REAL value +0 must be encoded with zero-length value block")
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBinaryFloat(v []byte) (float64, error) {
|
||||||
|
var info byte
|
||||||
|
var buf []byte
|
||||||
|
|
||||||
|
info, v = v[0], v[1:]
|
||||||
|
|
||||||
|
var base int
|
||||||
|
switch info & 0x30 {
|
||||||
|
case 0x00:
|
||||||
|
base = 2
|
||||||
|
case 0x10:
|
||||||
|
base = 8
|
||||||
|
case 0x20:
|
||||||
|
base = 16
|
||||||
|
case 0x30:
|
||||||
|
return 0.0, errors.New("bits 6 and 5 of information octet for REAL are equal to 11")
|
||||||
|
}
|
||||||
|
|
||||||
|
scale := uint((info & 0x0c) >> 2)
|
||||||
|
|
||||||
|
var expLen int
|
||||||
|
switch info & 0x03 {
|
||||||
|
case 0x00:
|
||||||
|
expLen = 1
|
||||||
|
case 0x01:
|
||||||
|
expLen = 2
|
||||||
|
case 0x02:
|
||||||
|
expLen = 3
|
||||||
|
case 0x03:
|
||||||
|
expLen = int(v[0])
|
||||||
|
if expLen > 8 {
|
||||||
|
return 0.0, errors.New("too big value of exponent")
|
||||||
|
}
|
||||||
|
v = v[1:]
|
||||||
|
}
|
||||||
|
buf, v = v[:expLen], v[expLen:]
|
||||||
|
exponent, err := ParseInt64(buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0.0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v) > 8 {
|
||||||
|
return 0.0, errors.New("too big value of mantissa")
|
||||||
|
}
|
||||||
|
|
||||||
|
mant, err := ParseInt64(v)
|
||||||
|
if err != nil {
|
||||||
|
return 0.0, err
|
||||||
|
}
|
||||||
|
mantissa := mant << scale
|
||||||
|
|
||||||
|
if info&0x40 == 0x40 {
|
||||||
|
mantissa = -mantissa
|
||||||
|
}
|
||||||
|
|
||||||
|
return float64(mantissa) * math.Pow(float64(base), float64(exponent)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDecimalFloat(v []byte) (val float64, err error) {
|
||||||
|
switch v[0] & 0x3F {
|
||||||
|
case 0x01: // NR form 1
|
||||||
|
var iVal int64
|
||||||
|
iVal, err = strconv.ParseInt(strings.TrimLeft(string(v[1:]), " "), 10, 64)
|
||||||
|
val = float64(iVal)
|
||||||
|
case 0x02, 0x03: // NR form 2, 3
|
||||||
|
val, err = strconv.ParseFloat(strings.Replace(strings.TrimLeft(string(v[1:]), " "), ",", ".", -1), 64)
|
||||||
|
default:
|
||||||
|
err = errors.New("incorrect NR form")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0.0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if val == 0.0 && math.Signbit(val) {
|
||||||
|
return 0.0, errors.New("REAL value -0 must be encoded as a special value")
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSpecialFloat(v []byte) (float64, error) {
|
||||||
|
if len(v) != 1 {
|
||||||
|
return 0.0, errors.New(`encoding of "special value" must not contain exponent and mantissa`)
|
||||||
|
}
|
||||||
|
switch v[0] {
|
||||||
|
case 0x40:
|
||||||
|
return math.Inf(1), nil
|
||||||
|
case 0x41:
|
||||||
|
return math.Inf(-1), nil
|
||||||
|
case 0x42:
|
||||||
|
return math.NaN(), nil
|
||||||
|
case 0x43:
|
||||||
|
return math.Copysign(0, -1), nil
|
||||||
|
}
|
||||||
|
return 0.0, errors.New(`encoding of "special value" not from ASN.1 standard`)
|
||||||
|
}
|
2
vendor/gopkg.in/asn1-ber.v1/util.go → vendor/github.com/go-asn1-ber/asn1-ber/util.go
generated
vendored
2
vendor/gopkg.in/asn1-ber.v1/util.go → vendor/github.com/go-asn1-ber/asn1-ber/util.go
generated
vendored
@ -0,0 +1,540 @@
|
|||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
enchex "encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Azure/go-ntlmssp"
|
||||||
|
ber "github.com/go-asn1-ber/asn1-ber"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SimpleBindRequest represents a username/password bind operation
|
||||||
|
type SimpleBindRequest struct {
|
||||||
|
// Username is the name of the Directory object that the client wishes to bind as
|
||||||
|
Username string
|
||||||
|
// Password is the credentials to bind with
|
||||||
|
Password string
|
||||||
|
// Controls are optional controls to send with the bind request
|
||||||
|
Controls []Control
|
||||||
|
// AllowEmptyPassword sets whether the client allows binding with an empty password
|
||||||
|
// (normally used for unauthenticated bind).
|
||||||
|
AllowEmptyPassword bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleBindResult contains the response from the server
|
||||||
|
type SimpleBindResult struct {
|
||||||
|
Controls []Control
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSimpleBindRequest returns a bind request
|
||||||
|
func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest {
|
||||||
|
return &SimpleBindRequest{
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
Controls: controls,
|
||||||
|
AllowEmptyPassword: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *SimpleBindRequest) appendTo(envelope *ber.Packet) error {
|
||||||
|
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
|
||||||
|
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
|
||||||
|
pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Username, "User Name"))
|
||||||
|
pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.Password, "Password"))
|
||||||
|
|
||||||
|
envelope.AppendChild(pkt)
|
||||||
|
if len(req.Controls) > 0 {
|
||||||
|
envelope.AppendChild(encodeControls(req.Controls))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleBind performs the simple bind operation defined in the given request
|
||||||
|
func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) {
|
||||||
|
if simpleBindRequest.Password == "" && !simpleBindRequest.AllowEmptyPassword {
|
||||||
|
return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
|
||||||
|
}
|
||||||
|
|
||||||
|
msgCtx, err := l.doRequest(simpleBindRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer l.finishMessage(msgCtx)
|
||||||
|
|
||||||
|
packet, err := l.readPacket(msgCtx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &SimpleBindResult{
|
||||||
|
Controls: make([]Control, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(packet.Children) == 3 {
|
||||||
|
for _, child := range packet.Children[2].Children {
|
||||||
|
decodedChild, decodeErr := DecodeControl(child)
|
||||||
|
if decodeErr != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode child control: %s", decodeErr)
|
||||||
|
}
|
||||||
|
result.Controls = append(result.Controls, decodedChild)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = GetLDAPError(packet)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind performs a bind with the given username and password.
|
||||||
|
//
|
||||||
|
// It does not allow unauthenticated bind (i.e. empty password). Use the UnauthenticatedBind method
|
||||||
|
// for that.
|
||||||
|
func (l *Conn) Bind(username, password string) error {
|
||||||
|
req := &SimpleBindRequest{
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
AllowEmptyPassword: false,
|
||||||
|
}
|
||||||
|
_, err := l.SimpleBind(req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnauthenticatedBind performs an unauthenticated bind.
|
||||||
|
//
|
||||||
|
// A username may be provided for trace (e.g. logging) purpose only, but it is normally not
|
||||||
|
// authenticated or otherwise validated by the LDAP server.
|
||||||
|
//
|
||||||
|
// See https://tools.ietf.org/html/rfc4513#section-5.1.2 .
|
||||||
|
// See https://tools.ietf.org/html/rfc4513#section-6.3.1 .
|
||||||
|
func (l *Conn) UnauthenticatedBind(username string) error {
|
||||||
|
req := &SimpleBindRequest{
|
||||||
|
Username: username,
|
||||||
|
Password: "",
|
||||||
|
AllowEmptyPassword: true,
|
||||||
|
}
|
||||||
|
_, err := l.SimpleBind(req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DigestMD5BindRequest represents a digest-md5 bind operation
|
||||||
|
type DigestMD5BindRequest struct {
|
||||||
|
Host string
|
||||||
|
// Username is the name of the Directory object that the client wishes to bind as
|
||||||
|
Username string
|
||||||
|
// Password is the credentials to bind with
|
||||||
|
Password string
|
||||||
|
// Controls are optional controls to send with the bind request
|
||||||
|
Controls []Control
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *DigestMD5BindRequest) appendTo(envelope *ber.Packet) 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"))
|
||||||
|
|
||||||
|
auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
|
||||||
|
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech"))
|
||||||
|
request.AppendChild(auth)
|
||||||
|
envelope.AppendChild(request)
|
||||||
|
if len(req.Controls) > 0 {
|
||||||
|
envelope.AppendChild(encodeControls(req.Controls))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DigestMD5BindResult contains the response from the server
|
||||||
|
type DigestMD5BindResult struct {
|
||||||
|
Controls []Control
|
||||||
|
}
|
||||||
|
|
||||||
|
// MD5Bind performs a digest-md5 bind with the given host, username and password.
|
||||||
|
func (l *Conn) MD5Bind(host, username, password string) error {
|
||||||
|
req := &DigestMD5BindRequest{
|
||||||
|
Host: host,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
_, err := l.DigestMD5Bind(req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DigestMD5Bind performs the digest-md5 bind operation defined in the given request
|
||||||
|
func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*DigestMD5BindResult, error) {
|
||||||
|
if digestMD5BindRequest.Password == "" {
|
||||||
|
return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
|
||||||
|
}
|
||||||
|
|
||||||
|
msgCtx, err := l.doRequest(digestMD5BindRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer l.finishMessage(msgCtx)
|
||||||
|
|
||||||
|
packet, err := l.readPacket(msgCtx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
||||||
|
if l.Debug {
|
||||||
|
if err = addLDAPDescriptions(packet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ber.PrintPacket(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &DigestMD5BindResult{
|
||||||
|
Controls: make([]Control, 0),
|
||||||
|
}
|
||||||
|
var params map[string]string
|
||||||
|
if len(packet.Children) == 2 {
|
||||||
|
if len(packet.Children[1].Children) == 4 {
|
||||||
|
child := packet.Children[1].Children[0]
|
||||||
|
if child.Tag != ber.TagEnumerated {
|
||||||
|
return result, GetLDAPError(packet)
|
||||||
|
}
|
||||||
|
if child.Value.(int64) != 14 {
|
||||||
|
return result, GetLDAPError(packet)
|
||||||
|
}
|
||||||
|
child = packet.Children[1].Children[3]
|
||||||
|
if child.Tag != ber.TagObjectDescriptor {
|
||||||
|
return result, GetLDAPError(packet)
|
||||||
|
}
|
||||||
|
if child.Data == nil {
|
||||||
|
return result, GetLDAPError(packet)
|
||||||
|
}
|
||||||
|
data, _ := ioutil.ReadAll(child.Data)
|
||||||
|
params, err = parseParams(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return result, fmt.Errorf("parsing digest-challenge: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if params != nil {
|
||||||
|
resp := computeResponse(
|
||||||
|
params,
|
||||||
|
"ldap/"+strings.ToLower(digestMD5BindRequest.Host),
|
||||||
|
digestMD5BindRequest.Username,
|
||||||
|
digestMD5BindRequest.Password,
|
||||||
|
)
|
||||||
|
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"))
|
||||||
|
|
||||||
|
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"))
|
||||||
|
|
||||||
|
auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
|
||||||
|
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech"))
|
||||||
|
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, resp, "Credentials"))
|
||||||
|
request.AppendChild(auth)
|
||||||
|
packet.AppendChild(request)
|
||||||
|
msgCtx, err = l.sendMessage(packet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("send message: %s", 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)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseParams(str string) (map[string]string, error) {
|
||||||
|
m := make(map[string]string)
|
||||||
|
var key, value string
|
||||||
|
var state int
|
||||||
|
for i := 0; i <= len(str); i++ {
|
||||||
|
switch state {
|
||||||
|
case 0: //reading key
|
||||||
|
if i == len(str) {
|
||||||
|
return nil, fmt.Errorf("syntax error on %d", i)
|
||||||
|
}
|
||||||
|
if str[i] != '=' {
|
||||||
|
key += string(str[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state = 1
|
||||||
|
case 1: //reading value
|
||||||
|
if i == len(str) {
|
||||||
|
m[key] = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch str[i] {
|
||||||
|
case ',':
|
||||||
|
m[key] = value
|
||||||
|
state = 0
|
||||||
|
key = ""
|
||||||
|
value = ""
|
||||||
|
case '"':
|
||||||
|
if value != "" {
|
||||||
|
return nil, fmt.Errorf("syntax error on %d", i)
|
||||||
|
}
|
||||||
|
state = 2
|
||||||
|
default:
|
||||||
|
value += string(str[i])
|
||||||
|
}
|
||||||
|
case 2: //inside quotes
|
||||||
|
if i == len(str) {
|
||||||
|
return nil, fmt.Errorf("syntax error on %d", i)
|
||||||
|
}
|
||||||
|
if str[i] != '"' {
|
||||||
|
value += string(str[i])
|
||||||
|
} else {
|
||||||
|
state = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeResponse(params map[string]string, uri, username, password string) string {
|
||||||
|
nc := "00000001"
|
||||||
|
qop := "auth"
|
||||||
|
cnonce := enchex.EncodeToString(randomBytes(16))
|
||||||
|
x := username + ":" + params["realm"] + ":" + password
|
||||||
|
y := md5Hash([]byte(x))
|
||||||
|
|
||||||
|
a1 := bytes.NewBuffer(y)
|
||||||
|
a1.WriteString(":" + params["nonce"] + ":" + cnonce)
|
||||||
|
if len(params["authzid"]) > 0 {
|
||||||
|
a1.WriteString(":" + params["authzid"])
|
||||||
|
}
|
||||||
|
a2 := bytes.NewBuffer([]byte("AUTHENTICATE"))
|
||||||
|
a2.WriteString(":" + uri)
|
||||||
|
ha1 := enchex.EncodeToString(md5Hash(a1.Bytes()))
|
||||||
|
ha2 := enchex.EncodeToString(md5Hash(a2.Bytes()))
|
||||||
|
|
||||||
|
kd := ha1
|
||||||
|
kd += ":" + params["nonce"]
|
||||||
|
kd += ":" + nc
|
||||||
|
kd += ":" + cnonce
|
||||||
|
kd += ":" + qop
|
||||||
|
kd += ":" + ha2
|
||||||
|
resp := enchex.EncodeToString(md5Hash([]byte(kd)))
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`username="%s",realm="%s",nonce="%s",cnonce="%s",nc=00000001,qop=%s,digest-uri="%s",response=%s`,
|
||||||
|
username,
|
||||||
|
params["realm"],
|
||||||
|
params["nonce"],
|
||||||
|
cnonce,
|
||||||
|
qop,
|
||||||
|
uri,
|
||||||
|
resp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func md5Hash(b []byte) []byte {
|
||||||
|
hasher := md5.New()
|
||||||
|
hasher.Write(b)
|
||||||
|
return hasher.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomBytes(len int) []byte {
|
||||||
|
b := make([]byte, len)
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
b[i] = byte(rand.Intn(256))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
var externalBindRequest = requestFunc(func(envelope *ber.Packet) error {
|
||||||
|
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
|
||||||
|
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
|
||||||
|
pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
|
||||||
|
|
||||||
|
saslAuth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
|
||||||
|
saslAuth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "EXTERNAL", "SASL Mech"))
|
||||||
|
saslAuth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "SASL Cred"))
|
||||||
|
|
||||||
|
pkt.AppendChild(saslAuth)
|
||||||
|
|
||||||
|
envelope.AppendChild(pkt)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// ExternalBind performs SASL/EXTERNAL authentication.
|
||||||
|
//
|
||||||
|
// Use ldap.DialURL("ldapi://") to connect to the Unix socket before ExternalBind.
|
||||||
|
//
|
||||||
|
// See https://tools.ietf.org/html/rfc4422#appendix-A
|
||||||
|
func (l *Conn) ExternalBind() error {
|
||||||
|
msgCtx, err := l.doRequest(externalBindRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer l.finishMessage(msgCtx)
|
||||||
|
|
||||||
|
packet, err := l.readPacket(msgCtx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetLDAPError(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NTLMBind performs an NTLMSSP bind leveraging https://github.com/Azure/go-ntlmssp
|
||||||
|
|
||||||
|
// NTLMBindRequest represents an NTLMSSP bind operation
|
||||||
|
type NTLMBindRequest struct {
|
||||||
|
// Domain is the AD Domain to authenticate too. If not specified, it will be grabbed from the NTLMSSP Challenge
|
||||||
|
Domain string
|
||||||
|
// Username is the name of the Directory object that the client wishes to bind as
|
||||||
|
Username string
|
||||||
|
// Password is the credentials to bind with
|
||||||
|
Password string
|
||||||
|
// Hash is the hex NTLM hash to bind with. Password or hash must be provided
|
||||||
|
Hash string
|
||||||
|
// Controls are optional controls to send with the bind request
|
||||||
|
Controls []Control
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *NTLMBindRequest) appendTo(envelope *ber.Packet) 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"))
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// append the generated NTLMSSP message as a TagEnumerated BER value
|
||||||
|
auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEnumerated, negMessage, "authentication")
|
||||||
|
request.AppendChild(auth)
|
||||||
|
envelope.AppendChild(request)
|
||||||
|
if len(req.Controls) > 0 {
|
||||||
|
envelope.AppendChild(encodeControls(req.Controls))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NTLMBindResult contains the response from the server
|
||||||
|
type NTLMBindResult struct {
|
||||||
|
Controls []Control
|
||||||
|
}
|
||||||
|
|
||||||
|
// NTLMBind performs an NTLMSSP Bind with the given domain, username and password
|
||||||
|
func (l *Conn) NTLMBind(domain, username, password string) error {
|
||||||
|
req := &NTLMBindRequest{
|
||||||
|
Domain: domain,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
_, err := l.NTLMChallengeBind(req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NTLMBindWithHash performs an NTLM Bind with an NTLM hash instead of plaintext password (pass-the-hash)
|
||||||
|
func (l *Conn) NTLMBindWithHash(domain, username, hash string) error {
|
||||||
|
req := &NTLMBindRequest{
|
||||||
|
Domain: domain,
|
||||||
|
Username: username,
|
||||||
|
Hash: hash,
|
||||||
|
}
|
||||||
|
_, err := l.NTLMChallengeBind(req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NTLMChallengeBind performs the NTLMSSP bind operation defined in the given request
|
||||||
|
func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindResult, error) {
|
||||||
|
if ntlmBindRequest.Password == "" && ntlmBindRequest.Hash == "" {
|
||||||
|
return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
|
||||||
|
}
|
||||||
|
|
||||||
|
msgCtx, err := l.doRequest(ntlmBindRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer l.finishMessage(msgCtx)
|
||||||
|
packet, err := l.readPacket(msgCtx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
||||||
|
if l.Debug {
|
||||||
|
if err = addLDAPDescriptions(packet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ber.PrintPacket(packet)
|
||||||
|
}
|
||||||
|
result := &NTLMBindResult{
|
||||||
|
Controls: make([]Control, 0),
|
||||||
|
}
|
||||||
|
var ntlmsspChallenge []byte
|
||||||
|
|
||||||
|
// now find the NTLM Response Message
|
||||||
|
if len(packet.Children) == 2 {
|
||||||
|
if len(packet.Children[1].Children) == 3 {
|
||||||
|
child := packet.Children[1].Children[1]
|
||||||
|
ntlmsspChallenge = child.ByteValue
|
||||||
|
// Check to make sure we got the right message. It will always start with NTLMSSP
|
||||||
|
if !bytes.Equal(ntlmsspChallenge[:7], []byte("NTLMSSP")) {
|
||||||
|
return result, GetLDAPError(packet)
|
||||||
|
}
|
||||||
|
l.Debug.Printf("%d: found ntlmssp challenge", msgCtx.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.Password != "" {
|
||||||
|
responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password)
|
||||||
|
} else if ntlmBindRequest.Hash != "" {
|
||||||
|
responseMessage, err = ntlmssp.ProcessChallengeWithHash(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Hash)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("need a password or hash to generate reply")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return result, fmt.Errorf("parsing 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"))
|
||||||
|
|
||||||
|
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"))
|
||||||
|
|
||||||
|
// append the challenge response message as a TagEmbeddedPDV BER value
|
||||||
|
auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEmbeddedPDV, responseMessage, "authentication")
|
||||||
|
|
||||||
|
request.AppendChild(auth)
|
||||||
|
packet.AppendChild(request)
|
||||||
|
msgCtx, err = l.sendMessage(packet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("send message: %s", 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)
|
||||||
|
return result, err
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client knows how to interact with an LDAP server
|
||||||
|
type Client interface {
|
||||||
|
Start()
|
||||||
|
StartTLS(*tls.Config) error
|
||||||
|
Close()
|
||||||
|
SetTimeout(time.Duration)
|
||||||
|
|
||||||
|
Bind(username, password string) error
|
||||||
|
UnauthenticatedBind(username string) error
|
||||||
|
SimpleBind(*SimpleBindRequest) (*SimpleBindResult, error)
|
||||||
|
ExternalBind() error
|
||||||
|
|
||||||
|
Add(*AddRequest) error
|
||||||
|
Del(*DelRequest) error
|
||||||
|
Modify(*ModifyRequest) error
|
||||||
|
ModifyDN(*ModifyDNRequest) error
|
||||||
|
|
||||||
|
Compare(dn, attribute, value string) (bool, error)
|
||||||
|
PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error)
|
||||||
|
|
||||||
|
Search(*SearchRequest) (*SearchResult, error)
|
||||||
|
SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
ber "github.com/go-asn1-ber/asn1-ber"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CompareRequest represents an LDAP CompareRequest operation.
|
||||||
|
type CompareRequest struct {
|
||||||
|
DN string
|
||||||
|
Attribute string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *CompareRequest) appendTo(envelope *ber.Packet) error {
|
||||||
|
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationCompareRequest, nil, "Compare Request")
|
||||||
|
pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN"))
|
||||||
|
|
||||||
|
ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion")
|
||||||
|
ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Attribute, "AttributeDesc"))
|
||||||
|
ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Value, "AssertionValue"))
|
||||||
|
|
||||||
|
pkt.AppendChild(ava)
|
||||||
|
|
||||||
|
envelope.AppendChild(pkt)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare checks to see if the attribute of the dn matches value. Returns true if it does otherwise
|
||||||
|
// false with any error that occurs if any.
|
||||||
|
func (l *Conn) Compare(dn, attribute, value string) (bool, error) {
|
||||||
|
msgCtx, err := l.doRequest(&CompareRequest{
|
||||||
|
DN: dn,
|
||||||
|
Attribute: attribute,
|
||||||
|
Value: value})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer l.finishMessage(msgCtx)
|
||||||
|
|
||||||
|
packet, err := l.readPacket(msgCtx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.Children[1].Tag == ApplicationCompareResponse {
|
||||||
|
err := GetLDAPError(packet)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case IsErrorWithCode(err, LDAPResultCompareTrue):
|
||||||
|
return true, nil
|
||||||
|
case IsErrorWithCode(err, LDAPResultCompareFalse):
|
||||||
|
return false, nil
|
||||||
|
default:
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag)
|
||||||
|
}
|
39
vendor/gopkg.in/ldap.v3/control.go → vendor/github.com/go-ldap/ldap/v3/control.go
generated
vendored
39
vendor/gopkg.in/ldap.v3/control.go → vendor/github.com/go-ldap/ldap/v3/control.go
generated
vendored
@ -0,0 +1,59 @@
|
|||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
ber "github.com/go-asn1-ber/asn1-ber"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DelRequest implements an LDAP deletion request
|
||||||
|
type DelRequest struct {
|
||||||
|
// DN is the name of the directory entry to delete
|
||||||
|
DN string
|
||||||
|
// Controls hold optional controls to send with the request
|
||||||
|
Controls []Control
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *DelRequest) appendTo(envelope *ber.Packet) error {
|
||||||
|
pkt := ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationDelRequest, req.DN, "Del Request")
|
||||||
|
pkt.Data.Write([]byte(req.DN))
|
||||||
|
|
||||||
|
envelope.AppendChild(pkt)
|
||||||
|
if len(req.Controls) > 0 {
|
||||||
|
envelope.AppendChild(encodeControls(req.Controls))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDelRequest creates a delete request for the given DN and controls
|
||||||
|
func NewDelRequest(DN string, Controls []Control) *DelRequest {
|
||||||
|
return &DelRequest{
|
||||||
|
DN: DN,
|
||||||
|
Controls: Controls,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Del executes the given delete request
|
||||||
|
func (l *Conn) Del(delRequest *DelRequest) error {
|
||||||
|
msgCtx, err := l.doRequest(delRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer l.finishMessage(msgCtx)
|
||||||
|
|
||||||
|
packet, err := l.readPacket(msgCtx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.Children[1].Tag == ApplicationDelResponse {
|
||||||
|
err := GetLDAPError(packet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
176
vendor/gopkg.in/ldap.v3/filter.go → vendor/github.com/go-ldap/ldap/v3/filter.go
generated
vendored
176
vendor/gopkg.in/ldap.v3/filter.go → vendor/github.com/go-ldap/ldap/v3/filter.go
generated
vendored
@ -0,0 +1,9 @@
|
|||||||
|
module github.com/go-ldap/ldap/v3
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.1
|
||||||
|
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
|
||||||
|
)
|
@ -0,0 +1,11 @@
|
|||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
||||||
|
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
@ -0,0 +1,80 @@
|
|||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
ber "github.com/go-asn1-ber/asn1-ber"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ModifyDNRequest holds the request to modify a DN
|
||||||
|
type ModifyDNRequest struct {
|
||||||
|
DN string
|
||||||
|
NewRDN string
|
||||||
|
DeleteOldRDN bool
|
||||||
|
NewSuperior string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewModifyDNRequest creates a new request which can be passed to ModifyDN().
|
||||||
|
//
|
||||||
|
// To move an object in the tree, set the "newSup" to the new parent entry DN. Use an
|
||||||
|
// empty string for just changing the object's RDN.
|
||||||
|
//
|
||||||
|
// For moving the object without renaming, the "rdn" must be the first
|
||||||
|
// RDN of the given DN.
|
||||||
|
//
|
||||||
|
// A call like
|
||||||
|
// mdnReq := NewModifyDNRequest("uid=someone,dc=example,dc=org", "uid=newname", true, "")
|
||||||
|
// will setup the request to just rename uid=someone,dc=example,dc=org to
|
||||||
|
// uid=newname,dc=example,dc=org.
|
||||||
|
func NewModifyDNRequest(dn string, rdn string, delOld bool, newSup string) *ModifyDNRequest {
|
||||||
|
return &ModifyDNRequest{
|
||||||
|
DN: dn,
|
||||||
|
NewRDN: rdn,
|
||||||
|
DeleteOldRDN: delOld,
|
||||||
|
NewSuperior: newSup,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *ModifyDNRequest) appendTo(envelope *ber.Packet) error {
|
||||||
|
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyDNRequest, nil, "Modify DN Request")
|
||||||
|
pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN"))
|
||||||
|
pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.NewRDN, "New RDN"))
|
||||||
|
if req.DeleteOldRDN {
|
||||||
|
buf := []byte{0xff}
|
||||||
|
pkt.AppendChild(ber.NewString(ber.ClassUniversal,ber.TypePrimitive,ber.TagBoolean, string(buf),"Delete old RDN"))
|
||||||
|
}else{
|
||||||
|
pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, req.DeleteOldRDN, "Delete old RDN"))
|
||||||
|
}
|
||||||
|
if req.NewSuperior != "" {
|
||||||
|
pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.NewSuperior, "New Superior"))
|
||||||
|
}
|
||||||
|
|
||||||
|
envelope.AppendChild(pkt)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifyDN renames the given DN and optionally move to another base (when the "newSup" argument
|
||||||
|
// to NewModifyDNRequest() is not "").
|
||||||
|
func (l *Conn) ModifyDN(m *ModifyDNRequest) error {
|
||||||
|
msgCtx, err := l.doRequest(m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer l.finishMessage(msgCtx)
|
||||||
|
|
||||||
|
packet, err := l.readPacket(msgCtx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.Children[1].Tag == ApplicationModifyDNResponse {
|
||||||
|
err := GetLDAPError(packet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
ber "github.com/go-asn1-ber/asn1-ber"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Change operation choices
|
||||||
|
const (
|
||||||
|
AddAttribute = 0
|
||||||
|
DeleteAttribute = 1
|
||||||
|
ReplaceAttribute = 2
|
||||||
|
IncrementAttribute = 3 // (https://tools.ietf.org/html/rfc4525)
|
||||||
|
)
|
||||||
|
|
||||||
|
// PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
|
||||||
|
type PartialAttribute struct {
|
||||||
|
// Type is the type of the partial attribute
|
||||||
|
Type string
|
||||||
|
// Vals are the values of the partial attribute
|
||||||
|
Vals []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PartialAttribute) encode() *ber.Packet {
|
||||||
|
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PartialAttribute")
|
||||||
|
seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, p.Type, "Type"))
|
||||||
|
set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
|
||||||
|
for _, value := range p.Vals {
|
||||||
|
set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
|
||||||
|
}
|
||||||
|
seq.AppendChild(set)
|
||||||
|
return seq
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
|
||||||
|
type Change struct {
|
||||||
|
// Operation is the type of change to be made
|
||||||
|
Operation uint
|
||||||
|
// Modification is the attribute to be modified
|
||||||
|
Modification PartialAttribute
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Change) encode() *ber.Packet {
|
||||||
|
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
|
||||||
|
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(c.Operation), "Operation"))
|
||||||
|
change.AppendChild(c.Modification.encode())
|
||||||
|
return change
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
|
||||||
|
type ModifyRequest struct {
|
||||||
|
// DN is the distinguishedName of the directory entry to modify
|
||||||
|
DN string
|
||||||
|
// Changes contain the attributes to modify
|
||||||
|
Changes []Change
|
||||||
|
// Controls hold optional controls to send with the request
|
||||||
|
Controls []Control
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add appends the given attribute to the list of changes to be made
|
||||||
|
func (req *ModifyRequest) Add(attrType string, attrVals []string) {
|
||||||
|
req.appendChange(AddAttribute, attrType, attrVals)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete appends the given attribute to the list of changes to be made
|
||||||
|
func (req *ModifyRequest) Delete(attrType string, attrVals []string) {
|
||||||
|
req.appendChange(DeleteAttribute, attrType, attrVals)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace appends the given attribute to the list of changes to be made
|
||||||
|
func (req *ModifyRequest) Replace(attrType string, attrVals []string) {
|
||||||
|
req.appendChange(ReplaceAttribute, attrType, attrVals)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment appends the given attribute to the list of changes to be made
|
||||||
|
func (req *ModifyRequest) Increment(attrType string, attrVal string) {
|
||||||
|
req.appendChange(IncrementAttribute, attrType, []string{attrVal})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *ModifyRequest) appendChange(operation uint, attrType string, attrVals []string) {
|
||||||
|
req.Changes = append(req.Changes, Change{operation, PartialAttribute{Type: attrType, Vals: attrVals}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *ModifyRequest) appendTo(envelope *ber.Packet) error {
|
||||||
|
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request")
|
||||||
|
pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN"))
|
||||||
|
changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes")
|
||||||
|
for _, change := range req.Changes {
|
||||||
|
changes.AppendChild(change.encode())
|
||||||
|
}
|
||||||
|
pkt.AppendChild(changes)
|
||||||
|
|
||||||
|
envelope.AppendChild(pkt)
|
||||||
|
if len(req.Controls) > 0 {
|
||||||
|
envelope.AppendChild(encodeControls(req.Controls))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewModifyRequest creates a modify request for the given DN
|
||||||
|
func NewModifyRequest(dn string, controls []Control) *ModifyRequest {
|
||||||
|
return &ModifyRequest{
|
||||||
|
DN: dn,
|
||||||
|
Controls: controls,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify performs the ModifyRequest
|
||||||
|
func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
|
||||||
|
msgCtx, err := l.doRequest(modifyRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer l.finishMessage(msgCtx)
|
||||||
|
|
||||||
|
packet, err := l.readPacket(msgCtx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.Children[1].Tag == ApplicationModifyResponse {
|
||||||
|
err := GetLDAPError(packet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
ber "github.com/go-asn1-ber/asn1-ber"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errRespChanClosed = errors.New("ldap: response channel closed")
|
||||||
|
errCouldNotRetMsg = errors.New("ldap: could not retrieve message")
|
||||||
|
)
|
||||||
|
|
||||||
|
type request interface {
|
||||||
|
appendTo(*ber.Packet) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type requestFunc func(*ber.Packet) error
|
||||||
|
|
||||||
|
func (f requestFunc) appendTo(p *ber.Packet) error {
|
||||||
|
return f(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Conn) doRequest(req request) (*messageContext, error) {
|
||||||
|
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"))
|
||||||
|
if err := req.appendTo(packet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.Debug {
|
||||||
|
l.Debug.PrintPacket(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgCtx, err := l.sendMessage(packet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l.Debug.Printf("%d: returning", msgCtx.id)
|
||||||
|
return msgCtx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Conn) readPacket(msgCtx *messageContext) (*ber.Packet, error) {
|
||||||
|
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
||||||
|
packetResponse, ok := <-msgCtx.responses
|
||||||
|
if !ok {
|
||||||
|
return nil, NewError(ErrorNetwork, errRespChanClosed)
|
||||||
|
}
|
||||||
|
packet, err := packetResponse.ReadPacket()
|
||||||
|
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet == nil {
|
||||||
|
return nil, NewError(ErrorNetwork, errCouldNotRetMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.Debug {
|
||||||
|
if err = addLDAPDescriptions(packet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l.Debug.PrintPacket(packet)
|
||||||
|
}
|
||||||
|
return packet, nil
|
||||||
|
}
|
176
vendor/gopkg.in/ldap.v3/search.go → vendor/github.com/go-ldap/ldap/v3/search.go
generated
vendored
176
vendor/gopkg.in/ldap.v3/search.go → vendor/github.com/go-ldap/ldap/v3/search.go
generated
vendored
@ -1,15 +0,0 @@
|
|||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.2
|
|
||||||
- 1.3
|
|
||||||
- 1.4
|
|
||||||
- 1.5
|
|
||||||
- tip
|
|
||||||
go_import_path: gopkg.in/asn-ber.v1
|
|
||||||
install:
|
|
||||||
- go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v
|
|
||||||
- go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v
|
|
||||||
- go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
|
|
||||||
- go build -v ./...
|
|
||||||
script:
|
|
||||||
- go test -v -cover ./...
|
|
@ -1,27 +0,0 @@
|
|||||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
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.
|
|
@ -1,32 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- "1.4.x"
|
|
||||||
- "1.5.x"
|
|
||||||
- "1.6.x"
|
|
||||||
- "1.7.x"
|
|
||||||
- "1.8.x"
|
|
||||||
- "1.9.x"
|
|
||||||
- "1.10.x"
|
|
||||||
- "1.11.x"
|
|
||||||
- "1.12.x"
|
|
||||||
- tip
|
|
||||||
|
|
||||||
git:
|
|
||||||
depth: 1
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
go_import_path: gopkg.in/ldap.v3
|
|
||||||
install:
|
|
||||||
- go get gopkg.in/asn1-ber.v1
|
|
||||||
- go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
|
|
||||||
- go get github.com/golang/lint/golint || go get golang.org/x/lint/golint || true
|
|
||||||
- go build -v ./...
|
|
||||||
script:
|
|
||||||
- make test
|
|
||||||
- make fmt
|
|
||||||
- make vet
|
|
||||||
- make lint
|
|
@ -1,12 +0,0 @@
|
|||||||
# Contribution Guidelines
|
|
||||||
|
|
||||||
We welcome contribution and improvements.
|
|
||||||
|
|
||||||
## Guiding Principles
|
|
||||||
|
|
||||||
To begin with here is a draft from an email exchange:
|
|
||||||
|
|
||||||
* take compatibility seriously (our semvers, compatibility with older go versions, etc)
|
|
||||||
* don't tag untested code for release
|
|
||||||
* beware of baking in implicit behavior based on other libraries/tools choices
|
|
||||||
* be as high-fidelity as possible in plumbing through LDAP data (don't mask errors or reduce power of someone using the library)
|
|
@ -1,82 +0,0 @@
|
|||||||
.PHONY: default install build test quicktest fmt vet lint
|
|
||||||
|
|
||||||
# List of all release tags "supported" by our current Go version
|
|
||||||
# E.g. ":go1.1:go1.2:go1.3:go1.4:go1.5:go1.6:go1.7:go1.8:go1.9:go1.10:go1.11:go1.12:"
|
|
||||||
GO_RELEASE_TAGS := $(shell go list -f ':{{join (context.ReleaseTags) ":"}}:' runtime)
|
|
||||||
|
|
||||||
# Only use the `-race` flag on newer versions of Go (version 1.3 and newer)
|
|
||||||
ifeq (,$(findstring :go1.3:,$(GO_RELEASE_TAGS)))
|
|
||||||
RACE_FLAG :=
|
|
||||||
else
|
|
||||||
RACE_FLAG := -race -cpu 1,2,4
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Run `go vet` on Go 1.12 and newer. For Go 1.5-1.11, use `go tool vet`
|
|
||||||
ifneq (,$(findstring :go1.12:,$(GO_RELEASE_TAGS)))
|
|
||||||
GO_VET := go vet \
|
|
||||||
-atomic \
|
|
||||||
-bool \
|
|
||||||
-copylocks \
|
|
||||||
-nilfunc \
|
|
||||||
-printf \
|
|
||||||
-rangeloops \
|
|
||||||
-unreachable \
|
|
||||||
-unsafeptr \
|
|
||||||
-unusedresult \
|
|
||||||
.
|
|
||||||
else ifneq (,$(findstring :go1.5:,$(GO_RELEASE_TAGS)))
|
|
||||||
GO_VET := go tool vet \
|
|
||||||
-atomic \
|
|
||||||
-bool \
|
|
||||||
-copylocks \
|
|
||||||
-nilfunc \
|
|
||||||
-printf \
|
|
||||||
-shadow \
|
|
||||||
-rangeloops \
|
|
||||||
-unreachable \
|
|
||||||
-unsafeptr \
|
|
||||||
-unusedresult \
|
|
||||||
.
|
|
||||||
else
|
|
||||||
GO_VET := @echo "go vet skipped -- not supported on this version of Go"
|
|
||||||
endif
|
|
||||||
|
|
||||||
default: fmt vet lint build quicktest
|
|
||||||
|
|
||||||
install:
|
|
||||||
go get -t -v ./...
|
|
||||||
|
|
||||||
build:
|
|
||||||
go build -v ./...
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test -v $(RACE_FLAG) -cover ./...
|
|
||||||
|
|
||||||
quicktest:
|
|
||||||
go test ./...
|
|
||||||
|
|
||||||
# Capture output and force failure when there is non-empty output
|
|
||||||
fmt:
|
|
||||||
@echo gofmt -l .
|
|
||||||
@OUTPUT=`gofmt -l . 2>&1`; \
|
|
||||||
if [ "$$OUTPUT" ]; then \
|
|
||||||
echo "gofmt must be run on the following files:"; \
|
|
||||||
echo "$$OUTPUT"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
vet:
|
|
||||||
$(GO_VET)
|
|
||||||
|
|
||||||
# https://github.com/golang/lint
|
|
||||||
# go get github.com/golang/lint/golint
|
|
||||||
# Capture output and force failure when there is non-empty output
|
|
||||||
# Only run on go1.5+
|
|
||||||
lint:
|
|
||||||
@echo golint ./...
|
|
||||||
@OUTPUT=`command -v golint >/dev/null 2>&1 && golint ./... 2>&1`; \
|
|
||||||
if [ "$$OUTPUT" ]; then \
|
|
||||||
echo "golint errors:"; \
|
|
||||||
echo "$$OUTPUT"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
@ -1,54 +0,0 @@
|
|||||||
[![GoDoc](https://godoc.org/gopkg.in/ldap.v3?status.svg)](https://godoc.org/gopkg.in/ldap.v3)
|
|
||||||
[![Build Status](https://travis-ci.org/go-ldap/ldap.svg)](https://travis-ci.org/go-ldap/ldap)
|
|
||||||
|
|
||||||
# Basic LDAP v3 functionality for the GO programming language.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
For the latest version use:
|
|
||||||
|
|
||||||
go get gopkg.in/ldap.v3
|
|
||||||
|
|
||||||
Import the latest version with:
|
|
||||||
|
|
||||||
import "gopkg.in/ldap.v3"
|
|
||||||
|
|
||||||
## Required Libraries:
|
|
||||||
|
|
||||||
- gopkg.in/asn1-ber.v1
|
|
||||||
|
|
||||||
## Features:
|
|
||||||
|
|
||||||
- Connecting to LDAP server (non-TLS, TLS, STARTTLS)
|
|
||||||
- Binding to LDAP server
|
|
||||||
- Searching for entries
|
|
||||||
- Filter Compile / Decompile
|
|
||||||
- Paging Search Results
|
|
||||||
- Modify Requests / Responses
|
|
||||||
- Add Requests / Responses
|
|
||||||
- Delete Requests / Responses
|
|
||||||
- Modify DN Requests / Responses
|
|
||||||
|
|
||||||
## Examples:
|
|
||||||
|
|
||||||
- search
|
|
||||||
- modify
|
|
||||||
|
|
||||||
## Contributing:
|
|
||||||
|
|
||||||
Bug reports and pull requests are welcome!
|
|
||||||
|
|
||||||
Before submitting a pull request, please make sure tests and verification scripts pass:
|
|
||||||
```
|
|
||||||
make all
|
|
||||||
```
|
|
||||||
|
|
||||||
To set up a pre-push hook to run the tests and verify scripts before pushing:
|
|
||||||
```
|
|
||||||
ln -s ../../.githooks/pre-push .git/hooks/pre-push
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
|
|
||||||
The design is licensed under the Creative Commons 3.0 Attributions license.
|
|
||||||
Read this article for more details: http://blog.golang.org/gopher
|
|
@ -1,135 +0,0 @@
|
|||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SimpleBindRequest represents a username/password bind operation
|
|
||||||
type SimpleBindRequest struct {
|
|
||||||
// Username is the name of the Directory object that the client wishes to bind as
|
|
||||||
Username string
|
|
||||||
// Password is the credentials to bind with
|
|
||||||
Password string
|
|
||||||
// Controls are optional controls to send with the bind request
|
|
||||||
Controls []Control
|
|
||||||
// AllowEmptyPassword sets whether the client allows binding with an empty password
|
|
||||||
// (normally used for unauthenticated bind).
|
|
||||||
AllowEmptyPassword bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// SimpleBindResult contains the response from the server
|
|
||||||
type SimpleBindResult struct {
|
|
||||||
Controls []Control
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSimpleBindRequest returns a bind request
|
|
||||||
func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest {
|
|
||||||
return &SimpleBindRequest{
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
Controls: controls,
|
|
||||||
AllowEmptyPassword: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bindRequest *SimpleBindRequest) encode() *ber.Packet {
|
|
||||||
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, bindRequest.Username, "User Name"))
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, bindRequest.Password, "Password"))
|
|
||||||
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
|
|
||||||
// SimpleBind performs the simple bind operation defined in the given request
|
|
||||||
func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) {
|
|
||||||
if simpleBindRequest.Password == "" && !simpleBindRequest.AllowEmptyPassword {
|
|
||||||
return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
|
|
||||||
}
|
|
||||||
|
|
||||||
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"))
|
|
||||||
encodedBindRequest := simpleBindRequest.encode()
|
|
||||||
packet.AppendChild(encodedBindRequest)
|
|
||||||
if len(simpleBindRequest.Controls) > 0 {
|
|
||||||
packet.AppendChild(encodeControls(simpleBindRequest.Controls))
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
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, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err = addLDAPDescriptions(packet); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &SimpleBindResult{
|
|
||||||
Controls: make([]Control, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(packet.Children) == 3 {
|
|
||||||
for _, child := range packet.Children[2].Children {
|
|
||||||
decodedChild, decodeErr := DecodeControl(child)
|
|
||||||
if decodeErr != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode child control: %s", decodeErr)
|
|
||||||
}
|
|
||||||
result.Controls = append(result.Controls, decodedChild)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = GetLDAPError(packet)
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind performs a bind with the given username and password.
|
|
||||||
//
|
|
||||||
// It does not allow unauthenticated bind (i.e. empty password). Use the UnauthenticatedBind method
|
|
||||||
// for that.
|
|
||||||
func (l *Conn) Bind(username, password string) error {
|
|
||||||
req := &SimpleBindRequest{
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
AllowEmptyPassword: false,
|
|
||||||
}
|
|
||||||
_, err := l.SimpleBind(req)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnauthenticatedBind performs an unauthenticated bind.
|
|
||||||
//
|
|
||||||
// A username may be provided for trace (e.g. logging) purpose only, but it is normally not
|
|
||||||
// authenticated or otherwise validated by the LDAP server.
|
|
||||||
//
|
|
||||||
// See https://tools.ietf.org/html/rfc4513#section-5.1.2 .
|
|
||||||
// See https://tools.ietf.org/html/rfc4513#section-6.3.1 .
|
|
||||||
func (l *Conn) UnauthenticatedBind(username string) error {
|
|
||||||
req := &SimpleBindRequest{
|
|
||||||
Username: username,
|
|
||||||
Password: "",
|
|
||||||
AllowEmptyPassword: true,
|
|
||||||
}
|
|
||||||
_, err := l.SimpleBind(req)
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client knows how to interact with an LDAP server
|
|
||||||
type Client interface {
|
|
||||||
Start()
|
|
||||||
StartTLS(config *tls.Config) error
|
|
||||||
Close()
|
|
||||||
SetTimeout(time.Duration)
|
|
||||||
|
|
||||||
Bind(username, password string) error
|
|
||||||
SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error)
|
|
||||||
|
|
||||||
Add(addRequest *AddRequest) error
|
|
||||||
Del(delRequest *DelRequest) error
|
|
||||||
Modify(modifyRequest *ModifyRequest) error
|
|
||||||
ModifyDN(modifyDNRequest *ModifyDNRequest) error
|
|
||||||
|
|
||||||
Compare(dn, attribute, value string) (bool, error)
|
|
||||||
PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error)
|
|
||||||
|
|
||||||
Search(searchRequest *SearchRequest) (*SearchResult, error)
|
|
||||||
SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
// File contains Compare functionality
|
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4511
|
|
||||||
//
|
|
||||||
// CompareRequest ::= [APPLICATION 14] SEQUENCE {
|
|
||||||
// entry LDAPDN,
|
|
||||||
// ava AttributeValueAssertion }
|
|
||||||
//
|
|
||||||
// AttributeValueAssertion ::= SEQUENCE {
|
|
||||||
// attributeDesc AttributeDescription,
|
|
||||||
// assertionValue AssertionValue }
|
|
||||||
//
|
|
||||||
// AttributeDescription ::= LDAPString
|
|
||||||
// -- Constrained to <attributedescription>
|
|
||||||
// -- [RFC4512]
|
|
||||||
//
|
|
||||||
// AttributeValue ::= OCTET STRING
|
|
||||||
//
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Compare checks to see if the attribute of the dn matches value. Returns true if it does otherwise
|
|
||||||
// false with any error that occurs if any.
|
|
||||||
func (l *Conn) Compare(dn, attribute, value string) (bool, error) {
|
|
||||||
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"))
|
|
||||||
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationCompareRequest, nil, "Compare Request")
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, dn, "DN"))
|
|
||||||
|
|
||||||
ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion")
|
|
||||||
ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "AttributeDesc"))
|
|
||||||
ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "AssertionValue"))
|
|
||||||
request.AppendChild(ava)
|
|
||||||
packet.AppendChild(request)
|
|
||||||
|
|
||||||
l.Debug.PrintPacket(packet)
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return false, 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 false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet.Children[1].Tag == ApplicationCompareResponse {
|
|
||||||
err := GetLDAPError(packet)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case IsErrorWithCode(err, LDAPResultCompareTrue):
|
|
||||||
return true, nil
|
|
||||||
case IsErrorWithCode(err, LDAPResultCompareFalse):
|
|
||||||
return false, nil
|
|
||||||
default:
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag)
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4511
|
|
||||||
//
|
|
||||||
// DelRequest ::= [APPLICATION 10] LDAPDN
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DelRequest implements an LDAP deletion request
|
|
||||||
type DelRequest struct {
|
|
||||||
// DN is the name of the directory entry to delete
|
|
||||||
DN string
|
|
||||||
// Controls hold optional controls to send with the request
|
|
||||||
Controls []Control
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d DelRequest) encode() *ber.Packet {
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationDelRequest, d.DN, "Del Request")
|
|
||||||
request.Data.Write([]byte(d.DN))
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDelRequest creates a delete request for the given DN and controls
|
|
||||||
func NewDelRequest(DN string,
|
|
||||||
Controls []Control) *DelRequest {
|
|
||||||
return &DelRequest{
|
|
||||||
DN: DN,
|
|
||||||
Controls: Controls,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Del executes the given delete request
|
|
||||||
func (l *Conn) Del(delRequest *DelRequest) error {
|
|
||||||
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"))
|
|
||||||
packet.AppendChild(delRequest.encode())
|
|
||||||
if len(delRequest.Controls) > 0 {
|
|
||||||
packet.AppendChild(encodeControls(delRequest.Controls))
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debug.PrintPacket(packet)
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return 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 err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet.Children[1].Tag == ApplicationDelResponse {
|
|
||||||
err := GetLDAPError(packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: returning", msgCtx.id)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
// Package ldap - moddn.go contains ModifyDN functionality
|
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4511
|
|
||||||
// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
|
|
||||||
// entry LDAPDN,
|
|
||||||
// newrdn RelativeLDAPDN,
|
|
||||||
// deleteoldrdn BOOLEAN,
|
|
||||||
// newSuperior [0] LDAPDN OPTIONAL }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ModifyDNRequest holds the request to modify a DN
|
|
||||||
type ModifyDNRequest struct {
|
|
||||||
DN string
|
|
||||||
NewRDN string
|
|
||||||
DeleteOldRDN bool
|
|
||||||
NewSuperior string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewModifyDNRequest creates a new request which can be passed to ModifyDN().
|
|
||||||
//
|
|
||||||
// To move an object in the tree, set the "newSup" to the new parent entry DN. Use an
|
|
||||||
// empty string for just changing the object's RDN.
|
|
||||||
//
|
|
||||||
// For moving the object without renaming, the "rdn" must be the first
|
|
||||||
// RDN of the given DN.
|
|
||||||
//
|
|
||||||
// A call like
|
|
||||||
// mdnReq := NewModifyDNRequest("uid=someone,dc=example,dc=org", "uid=newname", true, "")
|
|
||||||
// will setup the request to just rename uid=someone,dc=example,dc=org to
|
|
||||||
// uid=newname,dc=example,dc=org.
|
|
||||||
func NewModifyDNRequest(dn string, rdn string, delOld bool, newSup string) *ModifyDNRequest {
|
|
||||||
return &ModifyDNRequest{
|
|
||||||
DN: dn,
|
|
||||||
NewRDN: rdn,
|
|
||||||
DeleteOldRDN: delOld,
|
|
||||||
NewSuperior: newSup,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m ModifyDNRequest) encode() *ber.Packet {
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyDNRequest, nil, "Modify DN Request")
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.DN, "DN"))
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.NewRDN, "New RDN"))
|
|
||||||
request.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, m.DeleteOldRDN, "Delete old RDN"))
|
|
||||||
if m.NewSuperior != "" {
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, m.NewSuperior, "New Superior"))
|
|
||||||
}
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyDN renames the given DN and optionally move to another base (when the "newSup" argument
|
|
||||||
// to NewModifyDNRequest() is not "").
|
|
||||||
func (l *Conn) ModifyDN(m *ModifyDNRequest) error {
|
|
||||||
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"))
|
|
||||||
packet.AppendChild(m.encode())
|
|
||||||
|
|
||||||
l.Debug.PrintPacket(packet)
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return NewError(ErrorNetwork, errors.New("ldap: channel closed"))
|
|
||||||
}
|
|
||||||
packet, err = packetResponse.ReadPacket()
|
|
||||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet.Children[1].Tag == ApplicationModifyDNResponse {
|
|
||||||
err := GetLDAPError(packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: returning", msgCtx.id)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,173 +0,0 @@
|
|||||||
// File contains Modify functionality
|
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4511
|
|
||||||
//
|
|
||||||
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
|
|
||||||
// object LDAPDN,
|
|
||||||
// changes SEQUENCE OF change SEQUENCE {
|
|
||||||
// operation ENUMERATED {
|
|
||||||
// add (0),
|
|
||||||
// delete (1),
|
|
||||||
// replace (2),
|
|
||||||
// ... },
|
|
||||||
// modification PartialAttribute } }
|
|
||||||
//
|
|
||||||
// PartialAttribute ::= SEQUENCE {
|
|
||||||
// type AttributeDescription,
|
|
||||||
// vals SET OF value AttributeValue }
|
|
||||||
//
|
|
||||||
// AttributeDescription ::= LDAPString
|
|
||||||
// -- Constrained to <attributedescription>
|
|
||||||
// -- [RFC4512]
|
|
||||||
//
|
|
||||||
// AttributeValue ::= OCTET STRING
|
|
||||||
//
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Change operation choices
|
|
||||||
const (
|
|
||||||
AddAttribute = 0
|
|
||||||
DeleteAttribute = 1
|
|
||||||
ReplaceAttribute = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
|
|
||||||
type PartialAttribute struct {
|
|
||||||
// Type is the type of the partial attribute
|
|
||||||
Type string
|
|
||||||
// Vals are the values of the partial attribute
|
|
||||||
Vals []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PartialAttribute) encode() *ber.Packet {
|
|
||||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PartialAttribute")
|
|
||||||
seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, p.Type, "Type"))
|
|
||||||
set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
|
|
||||||
for _, value := range p.Vals {
|
|
||||||
set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
|
|
||||||
}
|
|
||||||
seq.AppendChild(set)
|
|
||||||
return seq
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
|
|
||||||
type Change struct {
|
|
||||||
// Operation is the type of change to be made
|
|
||||||
Operation uint
|
|
||||||
// Modification is the attribute to be modified
|
|
||||||
Modification PartialAttribute
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Change) encode() *ber.Packet {
|
|
||||||
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
|
|
||||||
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(c.Operation), "Operation"))
|
|
||||||
change.AppendChild(c.Modification.encode())
|
|
||||||
return change
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
|
|
||||||
type ModifyRequest struct {
|
|
||||||
// DN is the distinguishedName of the directory entry to modify
|
|
||||||
DN string
|
|
||||||
// Changes contain the attributes to modify
|
|
||||||
Changes []Change
|
|
||||||
// Controls hold optional controls to send with the request
|
|
||||||
Controls []Control
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add appends the given attribute to the list of changes to be made
|
|
||||||
func (m *ModifyRequest) Add(attrType string, attrVals []string) {
|
|
||||||
m.appendChange(AddAttribute, attrType, attrVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete appends the given attribute to the list of changes to be made
|
|
||||||
func (m *ModifyRequest) Delete(attrType string, attrVals []string) {
|
|
||||||
m.appendChange(DeleteAttribute, attrType, attrVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace appends the given attribute to the list of changes to be made
|
|
||||||
func (m *ModifyRequest) Replace(attrType string, attrVals []string) {
|
|
||||||
m.appendChange(ReplaceAttribute, attrType, attrVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ModifyRequest) appendChange(operation uint, attrType string, attrVals []string) {
|
|
||||||
m.Changes = append(m.Changes, Change{operation, PartialAttribute{Type: attrType, Vals: attrVals}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m ModifyRequest) encode() *ber.Packet {
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request")
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.DN, "DN"))
|
|
||||||
changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes")
|
|
||||||
for _, change := range m.Changes {
|
|
||||||
changes.AppendChild(change.encode())
|
|
||||||
}
|
|
||||||
request.AppendChild(changes)
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewModifyRequest creates a modify request for the given DN
|
|
||||||
func NewModifyRequest(
|
|
||||||
dn string,
|
|
||||||
controls []Control,
|
|
||||||
) *ModifyRequest {
|
|
||||||
return &ModifyRequest{
|
|
||||||
DN: dn,
|
|
||||||
Controls: controls,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modify performs the ModifyRequest
|
|
||||||
func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
|
|
||||||
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"))
|
|
||||||
packet.AppendChild(modifyRequest.encode())
|
|
||||||
if len(modifyRequest.Controls) > 0 {
|
|
||||||
packet.AppendChild(encodeControls(modifyRequest.Controls))
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debug.PrintPacket(packet)
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return 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 err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet.Children[1].Tag == ApplicationModifyResponse {
|
|
||||||
err := GetLDAPError(packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: returning", msgCtx.id)
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
Reference in new issue