[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>tokarchuk/v1.17
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