mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-24 08:27:27 -04:00
build(deps): bump github.com/go-ldap/ldap/v3 from 3.4.12 to 3.4.13
Bumps [github.com/go-ldap/ldap/v3](https://github.com/go-ldap/ldap) from 3.4.12 to 3.4.13. - [Release notes](https://github.com/go-ldap/ldap/releases) - [Commits](https://github.com/go-ldap/ldap/compare/v3.4.12...v3.4.13) --- updated-dependencies: - dependency-name: github.com/go-ldap/ldap/v3 dependency-version: 3.4.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
Ralf Haferkamp
parent
87a9660157
commit
9123e88f10
4
go.mod
4
go.mod
@@ -23,7 +23,7 @@ require (
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-jose/go-jose/v3 v3.0.4
|
||||
github.com/go-ldap/ldap/v3 v3.4.12
|
||||
github.com/go-ldap/ldap/v3 v3.4.13
|
||||
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3
|
||||
github.com/go-micro/plugins/v4/client/grpc v1.2.1
|
||||
github.com/go-micro/plugins/v4/logger/zerolog v1.2.0
|
||||
@@ -124,7 +124,7 @@ require (
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.1.0 // indirect
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -62,8 +62,8 @@ github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQ
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=
|
||||
github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
@@ -408,8 +408,8 @@ github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBj
|
||||
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-ldap/ldap/v3 v3.1.7/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
|
||||
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
|
||||
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3 h1:sfz1YppV05y4sYaW7kXZtrocU/+vimnIWt4cxAYh7+o=
|
||||
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3/go.mod h1:ZXFhGda43Z2TVbfGZefXyMJzsDHhCh0go3bZUcwTx7o=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
|
||||
2
vendor/github.com/Azure/go-ntlmssp/.gitignore
generated
vendored
Normal file
2
vendor/github.com/Azure/go-ntlmssp/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.vscode
|
||||
*.exe
|
||||
39
vendor/github.com/Azure/go-ntlmssp/.golangci.yml
generated
vendored
Normal file
39
vendor/github.com/Azure/go-ntlmssp/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
enable:
|
||||
- bodyclose
|
||||
- godox
|
||||
- nakedret
|
||||
- predeclared
|
||||
- unconvert
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
- internal/md4
|
||||
rules:
|
||||
- path: negotiate_flags.go
|
||||
linters:
|
||||
- unused
|
||||
- path: negotiator.go
|
||||
text: "QF1001:"
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
- internal/md4
|
||||
17
vendor/github.com/Azure/go-ntlmssp/.travis.yml
generated
vendored
17
vendor/github.com/Azure/go-ntlmssp/.travis.yml
generated
vendored
@@ -1,17 +0,0 @@
|
||||
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 ./...
|
||||
107
vendor/github.com/Azure/go-ntlmssp/E2E_README.md
generated
vendored
Normal file
107
vendor/github.com/Azure/go-ntlmssp/E2E_README.md
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
# E2E NTLM Tests
|
||||
|
||||
This directory contains end-to-end tests for the go-ntlmssp library that test against real NTLM servers.
|
||||
|
||||
## Running E2E Tests Locally
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Windows machine with IIS capabilities
|
||||
- Go 1.20 or later
|
||||
- Administrator privileges (for IIS setup)
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Enable IIS with Windows Authentication:**
|
||||
```powershell
|
||||
# Run as Administrator
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole -All
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-WindowsAuthentication -All
|
||||
```
|
||||
|
||||
2. **Create test site:**
|
||||
```powershell
|
||||
Import-Module WebAdministration
|
||||
New-Website -Name "ntlmtest" -Port 8080 -PhysicalPath "C:\inetpub\wwwroot"
|
||||
Set-WebConfigurationProperty -Filter "/system.webServer/security/authentication/anonymousAuthentication" -Name enabled -Value false -PSPath "IIS:\Sites\ntlmtest"
|
||||
Set-WebConfigurationProperty -Filter "/system.webServer/security/authentication/windowsAuthentication" -Name enabled -Value true -PSPath "IIS:\Sites\ntlmtest"
|
||||
```
|
||||
|
||||
3. **Set environment variables:**
|
||||
```powershell
|
||||
$env:NTLM_TEST_URL = "http://localhost:8080/"
|
||||
$env:NTLM_TEST_USER = "your_username"
|
||||
$env:NTLM_TEST_PASSWORD = "your_password"
|
||||
$env:NTLM_TEST_DOMAIN = "your_domain" # Optional
|
||||
```
|
||||
|
||||
> **Note**: The setup script automatically generates a random secure password if none is provided. For security, avoid hardcoded passwords in scripts or CI environments.
|
||||
|
||||
4. **Run tests:**
|
||||
```bash
|
||||
go test -v -tags=e2e ./e2e -run TestNTLM_E2E
|
||||
```
|
||||
|
||||
## GitHub Actions
|
||||
|
||||
The E2E tests run automatically in GitHub Actions on Windows runners. The workflow:
|
||||
|
||||
1. Sets up a clean Windows Server environment
|
||||
2. Generates a random secure password for the test user
|
||||
3. Creates a test user account with the random password
|
||||
4. Configures IIS with Windows Authentication
|
||||
5. Runs the E2E tests against the real NTLM server
|
||||
5. Cleans up resources
|
||||
|
||||
## Test Coverage
|
||||
|
||||
The E2E tests cover:
|
||||
|
||||
- ✅ Basic NTLM authentication flow
|
||||
- ✅ UPN format usernames (`user@domain.com`)
|
||||
- ✅ SAM format usernames (`DOMAIN\user`)
|
||||
- ✅ Authentication failure scenarios
|
||||
- ✅ Server accessibility checks
|
||||
- ✅ Context cancellation handling
|
||||
- ✅ Direct ProcessChallenge function testing
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `NTLM_TEST_URL` | URL of NTLM-enabled server | `http://localhost:8080/` |
|
||||
| `NTLM_TEST_USER` | Username for authentication | `$USERNAME` (Windows) |
|
||||
| `NTLM_TEST_PASSWORD` | Password for authentication | Required |
|
||||
| `NTLM_TEST_DOMAIN` | Domain for authentication | `$USERDOMAIN` (Windows) |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"No username available"** - Set `NTLM_TEST_USER` environment variable
|
||||
2. **"No password available"** - Set `NTLM_TEST_PASSWORD` environment variable
|
||||
3. **Connection refused** - Ensure IIS is running and accessible on the specified port
|
||||
4. **401 Unauthorized** - Check that Windows Authentication is enabled and working
|
||||
|
||||
### IIS Debugging
|
||||
|
||||
Check IIS status:
|
||||
```powershell
|
||||
Get-Website
|
||||
Get-WebApplication
|
||||
Get-WebConfigurationProperty -Filter "/system.webServer/security/authentication/windowsAuthentication" -Name enabled -PSPath "IIS:\Sites\Default Web Site"
|
||||
```
|
||||
|
||||
View IIS logs:
|
||||
```powershell
|
||||
Get-Content "C:\inetpub\logs\LogFiles\W3SVC1\*.log" | Select-Object -Last 50
|
||||
```
|
||||
|
||||
## Security Note
|
||||
|
||||
These tests use real authentication credentials. In CI/CD:
|
||||
- Test credentials are generated dynamically per job
|
||||
- Credentials are cleaned up after each test run
|
||||
- No persistent credentials are stored
|
||||
|
||||
For local development, use test accounts or ensure credentials are not committed to version control.
|
||||
22
vendor/github.com/Azure/go-ntlmssp/README.md
generated
vendored
22
vendor/github.com/Azure/go-ntlmssp/README.md
generated
vendored
@@ -1,22 +1,32 @@
|
||||
# go-ntlmssp
|
||||
Golang package that provides NTLM/Negotiate authentication over HTTP
|
||||
|
||||
[](https://godoc.org/github.com/Azure/go-ntlmssp) [](https://travis-ci.org/Azure/go-ntlmssp)
|
||||
[](https://pkg.go.dev/github.com/Azure/go-ntlmssp) [](https://github.com/Azure/go-ntlmssp/actions/workflows/test.yml)
|
||||
|
||||
Protocol details from https://msdn.microsoft.com/en-us/library/cc236621.aspx
|
||||
Implementation hints from http://davenport.sourceforge.net/ntlm.html
|
||||
Go package that provides NTLM/Negotiate authentication over HTTP
|
||||
|
||||
* NTLM protocol details from https://msdn.microsoft.com/en-us/library/cc236621.aspx
|
||||
* NTLM over HTTP details from https://datatracker.ietf.org/doc/html/rfc4559
|
||||
* 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.
|
||||
|
||||
# Installation
|
||||
|
||||
To install the package, use `go get`:
|
||||
|
||||
```bash
|
||||
go get github.com/Azure/go-ntlmssp
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
```
|
||||
```go
|
||||
url, user, password := "http://www.example.com/secrets", "robpike", "pw123"
|
||||
client := &http.Client{
|
||||
Transport: ntlmssp.Negotiator{
|
||||
RoundTripper:&http.Transport{},
|
||||
RoundTripper: &http.Transport{},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
161
vendor/github.com/Azure/go-ntlmssp/authenticate_message.go
generated
vendored
161
vendor/github.com/Azure/go-ntlmssp/authenticate_message.go
generated
vendored
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package ntlmssp
|
||||
|
||||
import (
|
||||
@@ -14,8 +17,9 @@ type authenicateMessage struct {
|
||||
LmChallengeResponse []byte
|
||||
NtChallengeResponse []byte
|
||||
|
||||
TargetName string
|
||||
UserName string
|
||||
DomainName string
|
||||
UserName string
|
||||
Workstation string
|
||||
|
||||
// only set if negotiateFlag_NTLMSSP_NEGOTIATE_KEY_EXCH
|
||||
EncryptedRandomSessionKey []byte
|
||||
@@ -29,20 +33,20 @@ type authenticateMessageFields struct {
|
||||
messageHeader
|
||||
LmChallengeResponse varField
|
||||
NtChallengeResponse varField
|
||||
TargetName varField
|
||||
DomainName varField
|
||||
UserName varField
|
||||
Workstation varField
|
||||
_ [8]byte
|
||||
NegotiateFlags negotiateFlags
|
||||
}
|
||||
|
||||
func (m authenicateMessage) MarshalBinary() ([]byte, error) {
|
||||
func (m *authenicateMessage) MarshalBinary() ([]byte, error) {
|
||||
if !m.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEUNICODE) {
|
||||
return nil, errors.New("Only unicode is supported")
|
||||
return nil, errors.New("only unicode is supported")
|
||||
}
|
||||
|
||||
target, user := toUnicode(m.TargetName), toUnicode(m.UserName)
|
||||
workstation := toUnicode("")
|
||||
domain, user := toUnicode(m.DomainName), toUnicode(m.UserName)
|
||||
workstation := toUnicode(m.Workstation)
|
||||
|
||||
ptr := binary.Size(&authenticateMessageFields{})
|
||||
f := authenticateMessageFields{
|
||||
@@ -50,7 +54,7 @@ func (m authenicateMessage) MarshalBinary() ([]byte, error) {
|
||||
NegotiateFlags: m.NegotiateFlags,
|
||||
LmChallengeResponse: newVarField(&ptr, len(m.LmChallengeResponse)),
|
||||
NtChallengeResponse: newVarField(&ptr, len(m.NtChallengeResponse)),
|
||||
TargetName: newVarField(&ptr, len(target)),
|
||||
DomainName: newVarField(&ptr, len(domain)),
|
||||
UserName: newVarField(&ptr, len(user)),
|
||||
Workstation: newVarField(&ptr, len(workstation)),
|
||||
}
|
||||
@@ -67,7 +71,7 @@ func (m authenicateMessage) MarshalBinary() ([]byte, error) {
|
||||
if err := binary.Write(&b, binary.LittleEndian, &m.NtChallengeResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Write(&b, binary.LittleEndian, &target); err != nil {
|
||||
if err := binary.Write(&b, binary.LittleEndian, &domain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Write(&b, binary.LittleEndian, &user); err != nil {
|
||||
@@ -80,34 +84,54 @@ func (m authenicateMessage) MarshalBinary() ([]byte, error) {
|
||||
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, domainNeeded bool) ([]byte, error) {
|
||||
if user == "" && password == "" {
|
||||
return nil, errors.New("Anonymous authentication not supported")
|
||||
func splitNameForAuth(username string) (user, domain string) {
|
||||
if strings.Contains(username, "\\") {
|
||||
ucomponents := strings.SplitN(username, "\\", 2)
|
||||
domain = ucomponents[0]
|
||||
user = ucomponents[1]
|
||||
} else if strings.Contains(username, "@") {
|
||||
user = username
|
||||
} else {
|
||||
user = username
|
||||
}
|
||||
return user, domain
|
||||
}
|
||||
|
||||
// AuthenticateMessageOptions contains optional parameters for the Authenticate message.
|
||||
type AuthenticateMessageOptions struct {
|
||||
WorkstationName string
|
||||
|
||||
// PasswordHashed indicates whether the provided password is already hashed.
|
||||
// If true, the password is expected to be in hexadecimal format.
|
||||
PasswordHashed bool
|
||||
}
|
||||
|
||||
// NewAuthenticateMessage creates a new AUTHENTICATE message in response to the CHALLENGE message that was received from the server.
|
||||
// The options parameter allows specifying additional settings for the message, it can be nil to use defaults.
|
||||
func NewAuthenticateMessage(challenge []byte, username, password string, options *AuthenticateMessageOptions) ([]byte, error) {
|
||||
if username == "" && password == "" {
|
||||
return nil, errors.New("anonymous authentication not supported")
|
||||
}
|
||||
|
||||
var cm challengeMessage
|
||||
if err := cm.UnmarshalBinary(challengeMessageData); err != nil {
|
||||
if err := cm.UnmarshalBinary(challenge); 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)")
|
||||
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)")
|
||||
}
|
||||
|
||||
if !domainNeeded {
|
||||
cm.TargetName = ""
|
||||
return nil, errors.New("key exchange requested but not supported (NTLMSSP_NEGOTIATE_KEY_EXCH)")
|
||||
}
|
||||
|
||||
am := authenicateMessage{
|
||||
UserName: user,
|
||||
TargetName: cm.TargetName,
|
||||
NegotiateFlags: cm.NegotiateFlags,
|
||||
}
|
||||
am.UserName, am.DomainName = splitNameForAuth(username)
|
||||
if options != nil {
|
||||
am.Workstation = options.WorkstationName
|
||||
}
|
||||
|
||||
timestamp := cm.TargetInfo[avIDMsvAvTimestamp]
|
||||
if timestamp == nil { // no time sent, take current time
|
||||
@@ -118,9 +142,24 @@ func ProcessChallenge(challengeMessageData []byte, user, password string, domain
|
||||
}
|
||||
|
||||
clientChallenge := make([]byte, 8)
|
||||
rand.Reader.Read(clientChallenge)
|
||||
if _, err := rand.Reader.Read(clientChallenge); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ntlmV2Hash := getNtlmV2Hash(password, user, cm.TargetName)
|
||||
var ntlmV2Hash []byte
|
||||
if options != nil && options.PasswordHashed {
|
||||
hashParts := strings.Split(password, ":")
|
||||
if len(hashParts) > 1 {
|
||||
password = hashParts[1]
|
||||
}
|
||||
hashBytes, err := hex.DecodeString(password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ntlmV2Hash = getNtlmV2Hashed(hashBytes, am.UserName, am.DomainName)
|
||||
} else {
|
||||
ntlmV2Hash = getNtlmV2Hash(password, am.UserName, am.DomainName)
|
||||
}
|
||||
|
||||
am.NtChallengeResponse = computeNtlmV2Response(ntlmV2Hash,
|
||||
cm.ServerChallenge[:], clientChallenge, timestamp, cm.TargetInfoRaw)
|
||||
@@ -132,56 +171,24 @@ func ProcessChallenge(challengeMessageData []byte, user, password string, domain
|
||||
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()
|
||||
// ProcessChallenge crafts an AUTHENTICATE message in response to the CHALLENGE message that was received from the server.
|
||||
// DomainNeeded is ignored, as the function extracts the domain from the username if needed.
|
||||
//
|
||||
// Deprecated: Use [NewAuthenticateMessage] instead.
|
||||
//
|
||||
//go:fix inline
|
||||
func ProcessChallenge(challengeMessageData []byte, username, password string, domainNeeded bool) ([]byte, error) {
|
||||
return NewAuthenticateMessage(challengeMessageData, username, password, nil)
|
||||
}
|
||||
|
||||
// ProcessChallengeWithHash is like ProcessChallenge but expects the password to be already hashed.
|
||||
// The hash should be provided in hexadecimal format.
|
||||
//
|
||||
// Deprecated: Use [NewAuthenticateMessage] with [AuthenticateMessageOptions.PasswordHashed] instead.
|
||||
//
|
||||
//go:fix inline
|
||||
func ProcessChallengeWithHash(challengeMessageData []byte, username, hash string) ([]byte, error) {
|
||||
return NewAuthenticateMessage(challengeMessageData, username, hash, &AuthenticateMessageOptions{
|
||||
PasswordHashed: true,
|
||||
})
|
||||
}
|
||||
|
||||
99
vendor/github.com/Azure/go-ntlmssp/authheader.go
generated
vendored
99
vendor/github.com/Azure/go-ntlmssp/authheader.go
generated
vendored
@@ -1,66 +1,65 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package ntlmssp
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type authheader []string
|
||||
var schemaPreference = [...]string{"NTLM", "Negotiate", "Basic"}
|
||||
|
||||
func (h authheader) IsBasic() bool {
|
||||
for _, s := range h {
|
||||
if strings.HasPrefix(string(s), "Basic ") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
type authheader struct {
|
||||
schema string
|
||||
data string
|
||||
}
|
||||
|
||||
func (h authheader) Basic() string {
|
||||
for _, s := range h {
|
||||
if strings.HasPrefix(string(s), "Basic ") {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h authheader) IsNegotiate() bool {
|
||||
for _, s := range h {
|
||||
if strings.HasPrefix(string(s), "Negotiate") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h authheader) IsNTLM() bool {
|
||||
for _, s := range h {
|
||||
if strings.HasPrefix(string(s), "NTLM") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h authheader) GetData() ([]byte, error) {
|
||||
for _, s := range h {
|
||||
if strings.HasPrefix(string(s), "NTLM") || strings.HasPrefix(string(s), "Negotiate") || strings.HasPrefix(string(s), "Basic ") {
|
||||
p := strings.Split(string(s), " ")
|
||||
if len(p) < 2 {
|
||||
return nil, nil
|
||||
// newAuthHeader extracts the authheader from the provided HTTP headers.
|
||||
// It selects the most preferred authentication scheme.
|
||||
// If no supported scheme is found, it returns an empty authheader.
|
||||
func newAuthHeader(req http.Header) authheader {
|
||||
auth := req.Values("Www-Authenticate")
|
||||
preferred, idx := -1, -1
|
||||
for i, s := range auth {
|
||||
for j, schema := range schemaPreference {
|
||||
if s == schema || strings.HasPrefix(s, schema+" ") {
|
||||
if preferred == -1 || j < preferred {
|
||||
preferred = j
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(string(p[1]))
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
if idx == -1 {
|
||||
return authheader{}
|
||||
}
|
||||
schema, data, _ := strings.Cut(auth[idx], " ")
|
||||
return authheader{
|
||||
schema: schema,
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// isNTLM returns true if the authheader schema is NTLM or Negotiate.
|
||||
func (h authheader) isNTLM() bool {
|
||||
return h.schema == "NTLM" || h.schema == "Negotiate"
|
||||
}
|
||||
|
||||
// isBasic returns true if the authheader schema is Basic.
|
||||
func (h authheader) isBasic() bool {
|
||||
return h.schema == "Basic"
|
||||
}
|
||||
|
||||
// token extracts and decodes the base64 token from the authheader.
|
||||
// It returns nil if the schema is not NTLM or Negotiate.
|
||||
func (h authheader) token() ([]byte, error) {
|
||||
if !h.isNTLM() {
|
||||
// Schema not supported for token extraction
|
||||
return nil, nil
|
||||
}
|
||||
// RFC4559 4.2 - The token is a base64-encoded value
|
||||
return base64.StdEncoding.DecodeString(h.data)
|
||||
}
|
||||
|
||||
3
vendor/github.com/Azure/go-ntlmssp/avids.go
generated
vendored
3
vendor/github.com/Azure/go-ntlmssp/avids.go
generated
vendored
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package ntlmssp
|
||||
|
||||
type avID uint16
|
||||
|
||||
9
vendor/github.com/Azure/go-ntlmssp/challenge_message.go
generated
vendored
9
vendor/github.com/Azure/go-ntlmssp/challenge_message.go
generated
vendored
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package ntlmssp
|
||||
|
||||
import (
|
||||
@@ -32,8 +35,8 @@ func (m *challengeMessage) UnmarshalBinary(data []byte) error {
|
||||
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.IsValid() {
|
||||
return fmt.Errorf("message is not a valid challenge message: %+v", m.messageHeader)
|
||||
}
|
||||
|
||||
if m.challengeMessageFields.TargetName.Len > 0 {
|
||||
@@ -72,7 +75,7 @@ func (m *challengeMessage) UnmarshalBinary(data []byte) error {
|
||||
return err
|
||||
}
|
||||
if n != int(l) {
|
||||
return fmt.Errorf("Expected to read %d bytes, got only %d", l, n)
|
||||
return fmt.Errorf("expected to read %d bytes, got only %d", l, n)
|
||||
}
|
||||
m.TargetInfo[id] = value
|
||||
}
|
||||
|
||||
21
vendor/github.com/Azure/go-ntlmssp/internal/md4/README.md
generated
vendored
Normal file
21
vendor/github.com/Azure/go-ntlmssp/internal/md4/README.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# MD4 Implementation
|
||||
|
||||
This package contains an identical copy of the MD4 hash implementation from Go's extended cryptography package (`golang.org/x/crypto/md4`).
|
||||
|
||||
## Why Vendored?
|
||||
|
||||
This MD4 implementation is vendored locally to avoid depending on the `golang.org/x/crypto` package, which can introduce version conflicts and dependency management issues in `go.mod`. By maintaining our own copy, we ensure:
|
||||
|
||||
- **Stability**: No external dependency version conflicts
|
||||
- **Simplicity**: Cleaner `go.mod` file without xcrypto dependency
|
||||
- **Control**: Full control over the implementation without external changes
|
||||
|
||||
## Source
|
||||
|
||||
The original implementation can be found at:
|
||||
- Package: `golang.org/x/crypto/md4`
|
||||
- Repository: https://github.com/golang/crypto
|
||||
|
||||
## Usage
|
||||
|
||||
This package is intended for internal use within the go-ntlmssp library only. The MD4 hash algorithm is required for NTLM authentication but should not be used for general cryptographic purposes as MD4 is considered cryptographically broken.
|
||||
113
vendor/github.com/Azure/go-ntlmssp/internal/md4/md4.go
generated
vendored
Normal file
113
vendor/github.com/Azure/go-ntlmssp/internal/md4/md4.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package md4 implements the MD4 hash algorithm as defined in RFC 1320.
|
||||
package md4
|
||||
|
||||
import (
|
||||
"hash"
|
||||
)
|
||||
|
||||
// The size of an MD4 checksum in bytes.
|
||||
const Size = 16
|
||||
|
||||
// The blocksize of MD4 in bytes.
|
||||
const BlockSize = 64
|
||||
|
||||
const (
|
||||
_Chunk = 64
|
||||
_Init0 = 0x67452301
|
||||
_Init1 = 0xEFCDAB89
|
||||
_Init2 = 0x98BADCFE
|
||||
_Init3 = 0x10325476
|
||||
)
|
||||
|
||||
// digest represents the partial evaluation of a checksum.
|
||||
type digest struct {
|
||||
s [4]uint32
|
||||
x [_Chunk]byte
|
||||
nx int
|
||||
len uint64
|
||||
}
|
||||
|
||||
func (d *digest) Reset() {
|
||||
d.s[0] = _Init0
|
||||
d.s[1] = _Init1
|
||||
d.s[2] = _Init2
|
||||
d.s[3] = _Init3
|
||||
d.nx = 0
|
||||
d.len = 0
|
||||
}
|
||||
|
||||
// New returns a new hash.Hash computing the MD4 checksum.
|
||||
func New() hash.Hash {
|
||||
d := new(digest)
|
||||
d.Reset()
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *digest) Size() int { return Size }
|
||||
|
||||
func (d *digest) BlockSize() int { return BlockSize }
|
||||
|
||||
func (d *digest) Write(p []byte) (nn int, err error) {
|
||||
nn = len(p)
|
||||
d.len += uint64(nn)
|
||||
if d.nx > 0 {
|
||||
n := len(p)
|
||||
if n > _Chunk-d.nx {
|
||||
n = _Chunk - d.nx
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
d.x[d.nx+i] = p[i]
|
||||
}
|
||||
d.nx += n
|
||||
if d.nx == _Chunk {
|
||||
_Block(d, d.x[0:])
|
||||
d.nx = 0
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
n := _Block(d, p)
|
||||
p = p[n:]
|
||||
if len(p) > 0 {
|
||||
d.nx = copy(d.x[:], p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d0 *digest) Sum(in []byte) []byte {
|
||||
// Make a copy of d0, so that caller can keep writing and summing.
|
||||
d := new(digest)
|
||||
*d = *d0
|
||||
|
||||
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
|
||||
len := d.len
|
||||
var tmp [64]byte
|
||||
tmp[0] = 0x80
|
||||
if len%64 < 56 {
|
||||
d.Write(tmp[0 : 56-len%64])
|
||||
} else {
|
||||
d.Write(tmp[0 : 64+56-len%64])
|
||||
}
|
||||
|
||||
// Length in bits.
|
||||
len <<= 3
|
||||
for i := uint(0); i < 8; i++ {
|
||||
tmp[i] = byte(len >> (8 * i))
|
||||
}
|
||||
d.Write(tmp[0:8])
|
||||
|
||||
if d.nx != 0 {
|
||||
panic("d.nx != 0")
|
||||
}
|
||||
|
||||
for _, s := range d.s {
|
||||
in = append(in, byte(s>>0))
|
||||
in = append(in, byte(s>>8))
|
||||
in = append(in, byte(s>>16))
|
||||
in = append(in, byte(s>>24))
|
||||
}
|
||||
return in
|
||||
}
|
||||
91
vendor/github.com/Azure/go-ntlmssp/internal/md4/md4block.go
generated
vendored
Normal file
91
vendor/github.com/Azure/go-ntlmssp/internal/md4/md4block.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// MD4 block step.
|
||||
// In its own file so that a faster assembly or C version
|
||||
// can be substituted easily.
|
||||
|
||||
package md4
|
||||
|
||||
import "math/bits"
|
||||
|
||||
var shift1 = []int{3, 7, 11, 19}
|
||||
var shift2 = []int{3, 5, 9, 13}
|
||||
var shift3 = []int{3, 9, 11, 15}
|
||||
|
||||
var xIndex2 = []uint{0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15}
|
||||
var xIndex3 = []uint{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
|
||||
|
||||
func _Block(dig *digest, p []byte) int {
|
||||
a := dig.s[0]
|
||||
b := dig.s[1]
|
||||
c := dig.s[2]
|
||||
d := dig.s[3]
|
||||
n := 0
|
||||
var X [16]uint32
|
||||
for len(p) >= _Chunk {
|
||||
aa, bb, cc, dd := a, b, c, d
|
||||
|
||||
j := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
X[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24
|
||||
j += 4
|
||||
}
|
||||
|
||||
// If this needs to be made faster in the future,
|
||||
// the usual trick is to unroll each of these
|
||||
// loops by a factor of 4; that lets you replace
|
||||
// the shift[] lookups with constants and,
|
||||
// with suitable variable renaming in each
|
||||
// unrolled body, delete the a, b, c, d = d, a, b, c
|
||||
// (or you can let the optimizer do the renaming).
|
||||
//
|
||||
// The index variables are uint so that % by a power
|
||||
// of two can be optimized easily by a compiler.
|
||||
|
||||
// Round 1.
|
||||
for i := uint(0); i < 16; i++ {
|
||||
x := i
|
||||
s := shift1[i%4]
|
||||
f := ((c ^ d) & b) ^ d
|
||||
a += f + X[x]
|
||||
a = bits.RotateLeft32(a, s)
|
||||
a, b, c, d = d, a, b, c
|
||||
}
|
||||
|
||||
// Round 2.
|
||||
for i := uint(0); i < 16; i++ {
|
||||
x := xIndex2[i]
|
||||
s := shift2[i%4]
|
||||
g := (b & c) | (b & d) | (c & d)
|
||||
a += g + X[x] + 0x5a827999
|
||||
a = bits.RotateLeft32(a, s)
|
||||
a, b, c, d = d, a, b, c
|
||||
}
|
||||
|
||||
// Round 3.
|
||||
for i := uint(0); i < 16; i++ {
|
||||
x := xIndex3[i]
|
||||
s := shift3[i%4]
|
||||
h := b ^ c ^ d
|
||||
a += h + X[x] + 0x6ed9eba1
|
||||
a = bits.RotateLeft32(a, s)
|
||||
a, b, c, d = d, a, b, c
|
||||
}
|
||||
|
||||
a += aa
|
||||
b += bb
|
||||
c += cc
|
||||
d += dd
|
||||
|
||||
p = p[_Chunk:]
|
||||
n += _Chunk
|
||||
}
|
||||
|
||||
dig.s[0] = a
|
||||
dig.s[1] = b
|
||||
dig.s[2] = c
|
||||
dig.s[3] = d
|
||||
return n
|
||||
}
|
||||
3
vendor/github.com/Azure/go-ntlmssp/messageheader.go
generated
vendored
3
vendor/github.com/Azure/go-ntlmssp/messageheader.go
generated
vendored
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package ntlmssp
|
||||
|
||||
import (
|
||||
|
||||
5
vendor/github.com/Azure/go-ntlmssp/negotiate_flags.go
generated
vendored
5
vendor/github.com/Azure/go-ntlmssp/negotiate_flags.go
generated
vendored
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package ntlmssp
|
||||
|
||||
type negotiateFlags uint32
|
||||
@@ -48,5 +51,5 @@ func (field negotiateFlags) Has(flags negotiateFlags) bool {
|
||||
}
|
||||
|
||||
func (field *negotiateFlags) Unset(flags negotiateFlags) {
|
||||
*field = *field ^ (*field & flags)
|
||||
*field ^= *field & flags
|
||||
}
|
||||
|
||||
27
vendor/github.com/Azure/go-ntlmssp/negotiate_message.go
generated
vendored
27
vendor/github.com/Azure/go-ntlmssp/negotiate_message.go
generated
vendored
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package ntlmssp
|
||||
|
||||
import (
|
||||
@@ -23,27 +26,33 @@ var defaultFlags = negotiateFlagNTLMSSPNEGOTIATETARGETINFO |
|
||||
negotiateFlagNTLMSSPNEGOTIATE56 |
|
||||
negotiateFlagNTLMSSPNEGOTIATE128 |
|
||||
negotiateFlagNTLMSSPNEGOTIATEUNICODE |
|
||||
negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY
|
||||
negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY |
|
||||
negotiateFlagNTLMSSPNEGOTIATENTLM |
|
||||
negotiateFlagNTLMSSPNEGOTIATEALWAYSSIGN
|
||||
|
||||
//NewNegotiateMessage creates a new NEGOTIATE message with the
|
||||
//flags that this package supports.
|
||||
func NewNegotiateMessage(domainName, workstationName string) ([]byte, error) {
|
||||
// NewNegotiateMessage creates a new NEGOTIATE message with the flags that this package supports.
|
||||
// Note that domain and workstation refer to the client machine, not the user that is authenticating.
|
||||
// It is recommended to leave them empty unless you know which are their correct values.
|
||||
//
|
||||
// The server may ignore these values, or may use them to infer that the client if running on the
|
||||
// same machine.
|
||||
func NewNegotiateMessage(domain, workstation string) ([]byte, error) {
|
||||
payloadOffset := expMsgBodyLen
|
||||
flags := defaultFlags
|
||||
|
||||
if domainName != "" {
|
||||
if domain != "" {
|
||||
flags |= negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED
|
||||
}
|
||||
|
||||
if workstationName != "" {
|
||||
if workstation != "" {
|
||||
flags |= negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED
|
||||
}
|
||||
|
||||
msg := negotiateMessageFields{
|
||||
messageHeader: newMessageHeader(1),
|
||||
NegotiateFlags: flags,
|
||||
Domain: newVarField(&payloadOffset, len(domainName)),
|
||||
Workstation: newVarField(&payloadOffset, len(workstationName)),
|
||||
Domain: newVarField(&payloadOffset, len(domain)),
|
||||
Workstation: newVarField(&payloadOffset, len(workstation)),
|
||||
Version: DefaultVersion(),
|
||||
}
|
||||
|
||||
@@ -55,7 +64,7 @@ func NewNegotiateMessage(domainName, workstationName string) ([]byte, error) {
|
||||
return nil, errors.New("incorrect body length")
|
||||
}
|
||||
|
||||
payload := strings.ToUpper(domainName + workstationName)
|
||||
payload := strings.ToUpper(domain + workstation)
|
||||
if _, err := b.WriteString(payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
392
vendor/github.com/Azure/go-ntlmssp/negotiator.go
generated
vendored
392
vendor/github.com/Azure/go-ntlmssp/negotiator.go
generated
vendored
@@ -1,151 +1,331 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package ntlmssp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetDomain : parse domain name from based on slashes in the input
|
||||
// Need to check for upn as well
|
||||
func GetDomain(user string) (string, string, bool) {
|
||||
domain := ""
|
||||
domainNeeded := false
|
||||
// negotiatorBody wraps an io.ReadSeeker to allow waiting for its closure
|
||||
// before rewinding and reusing it.
|
||||
type negotiatorBody struct {
|
||||
body io.ReadSeeker
|
||||
closed chan struct{}
|
||||
startPos int64
|
||||
}
|
||||
|
||||
if strings.Contains(user, "\\") {
|
||||
ucomponents := strings.SplitN(user, "\\", 2)
|
||||
// newNegotiatorBody creates a negotiatorBody from the provided io.Reader.
|
||||
// If the body is nil, it returns nil.
|
||||
// If the body is already an io.ReadSeeker, it uses it directly.
|
||||
// Otherwise, it reads the entire body into memory to allow rewinding.
|
||||
func newNegotiatorBody(body io.Reader) (*negotiatorBody, error) {
|
||||
if body == nil {
|
||||
return nil, nil
|
||||
}
|
||||
// Check if body is already seekable to avoid buffering large bodies
|
||||
if seeker, ok := body.(io.ReadSeeker); ok {
|
||||
// Remember the current position
|
||||
startPos, err := seeker.Seek(0, io.SeekCurrent)
|
||||
if err == nil {
|
||||
// Seeking succeeded, use the seekable body directly
|
||||
return &negotiatorBody{
|
||||
body: seeker,
|
||||
closed: make(chan struct{}, 1),
|
||||
startPos: startPos,
|
||||
}, nil
|
||||
}
|
||||
// Seeking failed (e.g., pipes), fallback to buffering
|
||||
}
|
||||
// For non-seekable bodies, buffer in memory as required
|
||||
data, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &negotiatorBody{
|
||||
body: bytes.NewReader(data),
|
||||
closed: make(chan struct{}, 1),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *negotiatorBody) Read(p []byte) (n int, err error) {
|
||||
if b == nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return b.body.Read(p)
|
||||
}
|
||||
|
||||
// Close signals that the body is no longer needed for the current request.
|
||||
// It allows the negotiator to rewind the body for potential reuse.
|
||||
// The underlying body is not closed here; use close() for that.
|
||||
func (b *negotiatorBody) Close() error {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case b.closed <- struct{}{}:
|
||||
default:
|
||||
// Already signaled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// close closes the underlying body if it implements io.Closer.
|
||||
func (b *negotiatorBody) close() {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
if closer, ok := b.body.(io.Closer); ok {
|
||||
_ = closer.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// rewind rewinds the body to the start position for reuse.
|
||||
func (b *negotiatorBody) rewind() error {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
// Wait for the body to be closed before rewinding
|
||||
<-b.closed
|
||||
_, err := b.body.Seek(b.startPos, io.SeekStart)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetDomain extracts the user domain from the username if present.
|
||||
//
|
||||
// Deprecated: Pass the username directly to [ProcessChallenge], it will handle domain extraction.
|
||||
// Don't pass the resulting domain to [NewNegotiateMessage], that function expects the client
|
||||
// machine domain, not the user domain.
|
||||
func GetDomain(username string) (user string, domain string, domainNeeded bool) {
|
||||
if strings.Contains(username, "\\") {
|
||||
ucomponents := strings.SplitN(username, "\\", 2)
|
||||
domain = ucomponents[0]
|
||||
user = ucomponents[1]
|
||||
domainNeeded = true
|
||||
} else if strings.Contains(user, "@") {
|
||||
} else if strings.Contains(username, "@") {
|
||||
user = username
|
||||
domainNeeded = false
|
||||
} else {
|
||||
user = username
|
||||
domainNeeded = true
|
||||
}
|
||||
return user, domain, domainNeeded
|
||||
}
|
||||
|
||||
//Negotiator is a http.Roundtripper decorator that automatically
|
||||
//converts basic authentication to NTLM/Negotiate authentication when appropriate.
|
||||
type Negotiator struct{ http.RoundTripper }
|
||||
// Negotiator is a [net/http.RoundTripper] decorator that automatically
|
||||
// converts basic authentication to NTLM/Negotiate authentication when appropriate.
|
||||
//
|
||||
// The credentials must be set using [net/http.Request.SetBasicAuth] on a per-request basis.
|
||||
//
|
||||
// By default, no credentials will be sent to the server unless it requests
|
||||
// Basic authentication and [Negotiator.AllowBasicAuth] is set to true.
|
||||
type Negotiator struct {
|
||||
// RoundTripper is the underlying round tripper to use.
|
||||
// If nil, http.DefaultTransport is used.
|
||||
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) {
|
||||
// AllowBasicAuth controls whether to send Basic authentication credentials
|
||||
// if the server requests it.
|
||||
//
|
||||
// If false (default), Basic authentication requests are ignored
|
||||
// and only NTLM/Negotiate authentication is performed.
|
||||
// If true, Basic authentication requests are honored.
|
||||
//
|
||||
// Only set this to true if you trust the server you are connecting to.
|
||||
// Basic authentication sends the credentials in clear text and may be
|
||||
// vulnerable to man-in-the-middle attacks and compromised servers.
|
||||
AllowBasicAuth bool
|
||||
|
||||
// WorkstationDomain is the domain of the client machine.
|
||||
// It is normally not needed to set this field.
|
||||
// It is passed to the negotiate message.
|
||||
WorkstationDomain string
|
||||
|
||||
// WorkstationName is the workstation name of the client machine.
|
||||
// It is passed to the negotiate and authenticate messages.
|
||||
// Useful for auditing purposes on the server side.
|
||||
WorkstationName string
|
||||
}
|
||||
|
||||
// RoundTrip sends the request to the server, handling any authentication
|
||||
// re-sends as needed.
|
||||
func (l Negotiator) RoundTrip(req *http.Request) (*http.Response, 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.Values("Authorization"))
|
||||
if !reqauth.IsBasic() {
|
||||
username, password, ok := req.BasicAuth()
|
||||
if !ok {
|
||||
return rt.RoundTrip(req)
|
||||
}
|
||||
reqauthBasic := reqauth.Basic()
|
||||
// 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()))
|
||||
id := identity{
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
// first try anonymous, in case the server still finds us
|
||||
// authenticated from previous traffic
|
||||
|
||||
req = req.Clone(req.Context()) // Clone the request to avoid modifying the original
|
||||
|
||||
// We need to buffer or seek the request body to handle authentication challenges
|
||||
// that require resending the body multiple times during the NTLM handshake.
|
||||
body, err := newNegotiatorBody(req.Body)
|
||||
if err != nil {
|
||||
if req.Body != nil {
|
||||
_ = req.Body.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer body.close()
|
||||
|
||||
// First try anonymous, in case the server still finds us authenticated from previous traffic
|
||||
req.Body = body
|
||||
req.Header.Del("Authorization")
|
||||
res, err = rt.RoundTrip(req)
|
||||
resp, err := rt.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != http.StatusUnauthorized {
|
||||
return res, err
|
||||
}
|
||||
resauth := authheader(res.Header.Values("Www-Authenticate"))
|
||||
if !resauth.IsNegotiate() && !resauth.IsNTLM() {
|
||||
// Unauthorized, Negotiate not requested, let's try with basic auth
|
||||
req.Header.Set("Authorization", string(reqauthBasic))
|
||||
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.Values("Www-Authenticate"))
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
// No authentication required, return the response as is
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if resauth.IsNegotiate() || resauth.IsNTLM() {
|
||||
// 401 with request:Basic and response:Negotiate
|
||||
io.Copy(ioutil.Discard, res.Body)
|
||||
res.Body.Close()
|
||||
// Note that from here on, the response returned in case of error or unsuccessful
|
||||
// negotiation is the one we just got from the server. This is to allow the caller
|
||||
// to do its own handling in case we can't do it in this roundtrip.
|
||||
originalResp := resp
|
||||
|
||||
// recycle credentials
|
||||
u, p, err := reqauth.GetBasicCreds()
|
||||
resauth := newAuthHeader(resp.Header)
|
||||
if l.AllowBasicAuth && resauth.isBasic() {
|
||||
// Basic auth requested instead of NTLM/Negotiate.
|
||||
//
|
||||
// Rewind the body, we will resend it.
|
||||
if body.rewind() != nil {
|
||||
return originalResp, nil
|
||||
}
|
||||
req.SetBasicAuth(id.username, id.password)
|
||||
resp, err := rt.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return originalResp, nil
|
||||
}
|
||||
|
||||
// get domain from username
|
||||
domain := ""
|
||||
u, domain, domainNeeded := GetDomain(u)
|
||||
|
||||
// send negotiate
|
||||
negotiateMessage, err := NewNegotiateMessage(domain, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
// Basic auth succeeded, return the new response
|
||||
drainResponse(originalResp)
|
||||
return resp, nil
|
||||
}
|
||||
if resauth.IsNTLM() {
|
||||
req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(negotiateMessage))
|
||||
} else {
|
||||
req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(negotiateMessage))
|
||||
resauth = newAuthHeader(resp.Header)
|
||||
if !resauth.isNTLM() {
|
||||
// No NTLM/Negotiate requested, return the response as is
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
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.Values("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, domainNeeded)
|
||||
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)
|
||||
// Server upgraded from Basic to NTLM/Negotiate (rare but possible)
|
||||
drainResponse(resp)
|
||||
// After Basic-to-NTLM upgrade, update originalResp to the NTLM-triggering response
|
||||
originalResp = resp
|
||||
} else if !resauth.isNTLM() {
|
||||
// No NTLM/Negotiate requested, return the response as is
|
||||
return originalResp, nil
|
||||
}
|
||||
|
||||
return res, err
|
||||
// Server requested Negotiate/NTLM, start handshake
|
||||
|
||||
// First step: send negotiate message
|
||||
resp = clientHandshake(rt, req, resauth.schema, l.WorkstationDomain, l.WorkstationName)
|
||||
if resp == nil {
|
||||
return originalResp, nil
|
||||
}
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
// We are expecting a 401 with challenge, but the server responded differently,
|
||||
// maybe it even accepted our negotiate message without further challenge, which is
|
||||
// valid per the spec (RFC 4559 Section 5).
|
||||
// Return the response as is, negotiation is over.
|
||||
drainResponse(originalResp)
|
||||
return resp, nil
|
||||
}
|
||||
resauth = newAuthHeader(resp.Header)
|
||||
drainResponse(resp)
|
||||
|
||||
// Second step: process challenge and resend the original body with the authenticate message
|
||||
resp = completeHandshake(rt, resauth, req, id, l.WorkstationName)
|
||||
if resp == nil {
|
||||
return originalResp, nil
|
||||
}
|
||||
// We could return the original response in case of 401 again, but at this point
|
||||
// it's better to return the latest response from the server, as it might be the case
|
||||
// that we are really not authorized.
|
||||
drainResponse(originalResp) // Done with the original response
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type identity struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func drainResponse(res *http.Response) {
|
||||
// Drain body and close it to allow reusing the connection
|
||||
_, _ = io.Copy(io.Discard, res.Body)
|
||||
_ = res.Body.Close()
|
||||
}
|
||||
|
||||
func rewindBody(req *http.Request) error {
|
||||
if req.Body == nil {
|
||||
return nil
|
||||
}
|
||||
if nb, ok := req.Body.(*negotiatorBody); ok {
|
||||
return nb.rewind()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clientHandshake(rt http.RoundTripper, req *http.Request, schema string, domain, workstation string) *http.Response {
|
||||
if rewindBody(req) != nil {
|
||||
return nil
|
||||
}
|
||||
auth, err := NewNegotiateMessage(domain, workstation)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
req.Header.Set("Authorization", schema+" "+base64.StdEncoding.EncodeToString(auth))
|
||||
res, err := rt.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func completeHandshake(rt http.RoundTripper, resauth authheader, req *http.Request, id identity, workstation string) *http.Response {
|
||||
if rewindBody(req) != nil {
|
||||
return nil
|
||||
}
|
||||
challenge, err := resauth.token()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !resauth.isNTLM() || len(challenge) == 0 {
|
||||
// The only expected schema here is NTLM/Negotiate with a challenge token,
|
||||
// otherwise the negotiation is over.
|
||||
return nil
|
||||
}
|
||||
var opts *AuthenticateMessageOptions
|
||||
if workstation != "" {
|
||||
opts = &AuthenticateMessageOptions{
|
||||
WorkstationName: workstation,
|
||||
}
|
||||
}
|
||||
auth, err := NewAuthenticateMessage(challenge, id.username, id.password, opts)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
req.Header.Set("Authorization", resauth.schema+" "+base64.StdEncoding.EncodeToString(auth))
|
||||
resp, err := rt.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
20
vendor/github.com/Azure/go-ntlmssp/nlmp.go
generated
vendored
20
vendor/github.com/Azure/go-ntlmssp/nlmp.go
generated
vendored
@@ -1,5 +1,6 @@
|
||||
// Package ntlmssp provides NTLM/Negotiate authentication over HTTP
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// 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
|
||||
@@ -10,12 +11,17 @@ package ntlmssp
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"golang.org/x/crypto/md4"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-ntlmssp/internal/md4"
|
||||
)
|
||||
|
||||
func getNtlmV2Hash(password, username, target string) []byte {
|
||||
return hmacMd5(getNtlmHash(password), toUnicode(strings.ToUpper(username)+target))
|
||||
func getNtlmV2Hash(password, username, domain string) []byte {
|
||||
return getNtlmV2Hashed(getNtlmHash(password), username, domain)
|
||||
}
|
||||
|
||||
func getNtlmV2Hashed(ntlmHash []byte, username, domain string) []byte {
|
||||
return hmacMd5(ntlmHash, toUnicode(strings.ToUpper(username)+domain))
|
||||
}
|
||||
|
||||
func getNtlmHash(password string) []byte {
|
||||
@@ -25,8 +31,8 @@ func getNtlmHash(password string) []byte {
|
||||
}
|
||||
|
||||
func computeNtlmV2Response(ntlmV2Hash, serverChallenge, clientChallenge,
|
||||
timestamp, targetInfo []byte) []byte {
|
||||
|
||||
timestamp, targetInfo []byte,
|
||||
) []byte {
|
||||
temp := []byte{1, 1, 0, 0, 0, 0, 0, 0}
|
||||
temp = append(temp, timestamp...)
|
||||
temp = append(temp, clientChallenge...)
|
||||
|
||||
7
vendor/github.com/Azure/go-ntlmssp/unicode.go
generated
vendored
7
vendor/github.com/Azure/go-ntlmssp/unicode.go
generated
vendored
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package ntlmssp
|
||||
|
||||
import (
|
||||
@@ -11,7 +14,7 @@ import (
|
||||
|
||||
func fromUnicode(d []byte) (string, error) {
|
||||
if len(d)%2 > 0 {
|
||||
return "", errors.New("Unicode (UTF 16 LE) specified, but uneven data length")
|
||||
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)
|
||||
@@ -24,6 +27,6 @@ func fromUnicode(d []byte) (string, error) {
|
||||
func toUnicode(s string) []byte {
|
||||
uints := utf16.Encode([]rune(s))
|
||||
b := bytes.Buffer{}
|
||||
binary.Write(&b, binary.LittleEndian, &uints)
|
||||
_ = binary.Write(&b, binary.LittleEndian, &uints)
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
5
vendor/github.com/Azure/go-ntlmssp/varfield.go
generated
vendored
5
vendor/github.com/Azure/go-ntlmssp/varfield.go
generated
vendored
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package ntlmssp
|
||||
|
||||
import (
|
||||
@@ -12,7 +15,7 @@ type varField struct {
|
||||
|
||||
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 nil, errors.New("error reading data, varField extends beyond buffer")
|
||||
}
|
||||
return buffer[f.BufferOffset : f.BufferOffset+uint32(f.Len)], nil
|
||||
}
|
||||
|
||||
3
vendor/github.com/Azure/go-ntlmssp/version.go
generated
vendored
3
vendor/github.com/Azure/go-ntlmssp/version.go
generated
vendored
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package ntlmssp
|
||||
|
||||
// Version is a struct representing https://msdn.microsoft.com/en-us/library/cc236654.aspx
|
||||
|
||||
12
vendor/github.com/go-ldap/ldap/v3/control.go
generated
vendored
12
vendor/github.com/go-ldap/ldap/v3/control.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
@@ -880,7 +881,16 @@ func (c *ControlDirSync) Encode() *ber.Packet {
|
||||
|
||||
val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)")
|
||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value")
|
||||
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.Flags), "Flags"))
|
||||
|
||||
// Note: Active Directory expects a 4-byte unsigned integer for flags, but ASN.1 uses signed integers by default.
|
||||
// As a result, the BER encoder may encode flags as a 5-byte signed integer; we force 4-byte encoding here.
|
||||
flagsPacket := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, nil, "Flags")
|
||||
flagsPacket.Value = int64(c.Flags)
|
||||
flagsBytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(flagsBytes, uint32(c.Flags))
|
||||
flagsPacket.Data.Write(flagsBytes)
|
||||
seq.AppendChild(flagsPacket)
|
||||
|
||||
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCount), "MaxAttrCount"))
|
||||
seq.AppendChild(cookie)
|
||||
val.AppendChild(seq)
|
||||
|
||||
7
vendor/github.com/go-ldap/ldap/v3/error.go
generated
vendored
7
vendor/github.com/go-ldap/ldap/v3/error.go
generated
vendored
@@ -210,6 +210,10 @@ func GetLDAPError(packet *ber.Packet) error {
|
||||
}
|
||||
if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 {
|
||||
if ber.Type(response.Children[0].Tag) == ber.Type(ber.TagInteger) || ber.Type(response.Children[0].Tag) == ber.Type(ber.TagEnumerated) {
|
||||
if response.Children[0].Value == nil {
|
||||
return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("Invalid result code in packet"), Packet: packet}
|
||||
}
|
||||
|
||||
resultCode := uint16(response.Children[0].Value.(int64))
|
||||
if resultCode == 0 { // No error
|
||||
return nil
|
||||
@@ -217,6 +221,9 @@ func GetLDAPError(packet *ber.Packet) error {
|
||||
|
||||
if ber.Type(response.Children[1].Tag) == ber.Type(ber.TagOctetString) &&
|
||||
ber.Type(response.Children[2].Tag) == ber.Type(ber.TagOctetString) {
|
||||
if response.Children[1].Value == nil {
|
||||
return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("Invalid matchedDN in packet"), Packet: packet}
|
||||
}
|
||||
return &Error{
|
||||
ResultCode: resultCode,
|
||||
MatchedDN: response.Children[1].Value.(string),
|
||||
|
||||
19
vendor/github.com/go-ldap/ldap/v3/extended.go
generated
vendored
19
vendor/github.com/go-ldap/ldap/v3/extended.go
generated
vendored
@@ -76,18 +76,27 @@ func (l *Conn) Extended(er *ExtendedRequest) (*ExtendedResponse, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(packet.Children[1].Children) < 4 {
|
||||
extResp := packet.Children[1]
|
||||
if len(extResp.Children) < 3 {
|
||||
return nil, fmt.Errorf(
|
||||
"ldap: malformed extended response: expected 4 children, got %d",
|
||||
"ldap: malformed extended response: expected at least 3 children, got %d",
|
||||
len(packet.Children),
|
||||
)
|
||||
}
|
||||
|
||||
response := &ExtendedResponse{
|
||||
Name: packet.Children[1].Children[3].Data.String(),
|
||||
Controls: make([]Control, 0),
|
||||
}
|
||||
|
||||
for _, child := range extResp.Children {
|
||||
switch child.Tag {
|
||||
case ber.TagEnumerated:
|
||||
response.Name = child.Data.String()
|
||||
case ber.TagEmbeddedPDV:
|
||||
response.Value = child
|
||||
}
|
||||
}
|
||||
|
||||
if len(packet.Children) == 3 {
|
||||
for _, child := range packet.Children[2].Children {
|
||||
decodedChild, decodeErr := DecodeControl(child)
|
||||
@@ -98,9 +107,5 @@ func (l *Conn) Extended(er *ExtendedRequest) (*ExtendedResponse, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(packet.Children[1].Children) == 5 {
|
||||
response.Value = packet.Children[1].Children[4]
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
130
vendor/github.com/go-ldap/ldap/v3/postaladdress.go
generated
vendored
Normal file
130
vendor/github.com/go-ldap/ldap/v3/postaladdress.go
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrEmptyPostalAddress = errors.New("ldap: postal address cannot be empty")
|
||||
|
||||
// PostalAddress represents an RFC 4517 Postal Address
|
||||
// A postal address is a sequence of strings of one or more arbitrary UCS
|
||||
// characters, which form the lines of the address.
|
||||
type PostalAddress struct {
|
||||
lines []string
|
||||
}
|
||||
|
||||
// NewPostalAddress creates a new PostalAddress by copying non-empty lines from the provided slice of strings.
|
||||
func NewPostalAddress(lines []string) (*PostalAddress, error) {
|
||||
copiedLines := make([]string, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
copiedLines = append(copiedLines, line)
|
||||
}
|
||||
|
||||
if len(copiedLines) == 0 {
|
||||
return nil, ErrEmptyPostalAddress
|
||||
}
|
||||
|
||||
return &PostalAddress{lines: copiedLines}, nil
|
||||
}
|
||||
|
||||
// Lines returns a copy of the address lines as a slice of strings.
|
||||
func (p *PostalAddress) Lines() []string {
|
||||
copiedLines := make([]string, len(p.lines))
|
||||
copy(copiedLines, p.lines)
|
||||
return copiedLines
|
||||
}
|
||||
|
||||
// String returns the postal address as a single string, with lines joined by newline characters.
|
||||
func (p *PostalAddress) String() string {
|
||||
return strings.Join(p.lines, "\n")
|
||||
}
|
||||
|
||||
// Escape encodes special characters in the PostalAddress lines as per RFC 4517 and appends a `$` at the end of each line.
|
||||
func (p *PostalAddress) Escape() string {
|
||||
builder := &strings.Builder{}
|
||||
|
||||
for _, line := range p.lines {
|
||||
for _, char := range line {
|
||||
switch char {
|
||||
case '\\':
|
||||
builder.WriteString("\\5C")
|
||||
case '$':
|
||||
builder.WriteString("\\24")
|
||||
default:
|
||||
builder.WriteRune(char)
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteRune('$')
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// ParsePostalAddress parses an RFC 4517 escaped postal address string into a PostalAddress object or returns an error.
|
||||
func ParsePostalAddress(escaped string) (*PostalAddress, error) {
|
||||
lines := strings.Split(escaped, "$")
|
||||
parsedLines := make([]string, 0, len(lines))
|
||||
const totalEscapeLen = 3
|
||||
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
// Skip empty lines
|
||||
continue
|
||||
}
|
||||
|
||||
builder := &strings.Builder{}
|
||||
for i := 0; i < len(line); i++ {
|
||||
char := line[i]
|
||||
if char == '\\' && i+totalEscapeLen <= len(line) {
|
||||
escapeSeq := line[i+1 : i+totalEscapeLen]
|
||||
switch escapeSeq {
|
||||
case "5C", "5c":
|
||||
builder.WriteRune('\\')
|
||||
i += 2
|
||||
case "24":
|
||||
builder.WriteRune('$')
|
||||
i += 2
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid escape sequence: \\%s at position %d", escapeSeq, i)
|
||||
}
|
||||
} else if char == '\\' {
|
||||
return nil, fmt.Errorf("incomplete escape sequence at position %d", i)
|
||||
} else {
|
||||
builder.WriteByte(char)
|
||||
}
|
||||
}
|
||||
parsedLines = append(parsedLines, builder.String())
|
||||
}
|
||||
|
||||
if len(parsedLines) == 0 {
|
||||
return nil, ErrEmptyPostalAddress
|
||||
}
|
||||
|
||||
return &PostalAddress{lines: parsedLines}, nil
|
||||
}
|
||||
|
||||
// Equal compares the current PostalAddress with another PostalAddress and returns true if they are identical.
|
||||
func (p *PostalAddress) Equal(other *PostalAddress) bool {
|
||||
if p == other {
|
||||
return true
|
||||
}
|
||||
if p == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(p.lines) != len(other.lines) {
|
||||
return false
|
||||
}
|
||||
for i := range p.lines {
|
||||
if p.lines[i] != other.lines[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
2
vendor/github.com/go-ldap/ldap/v3/search.go
generated
vendored
2
vendor/github.com/go-ldap/ldap/v3/search.go
generated
vendored
@@ -623,7 +623,7 @@ func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
|
||||
|
||||
// SearchAsync performs a search request and returns all search results asynchronously.
|
||||
// This means you get all results until an error happens (or the search successfully finished),
|
||||
// e.g. for size / time limited requests all are recieved until the limit is reached.
|
||||
// e.g. for size / time limited requests all are received until the limit is reached.
|
||||
// To stop the search, call cancel function of the context.
|
||||
func (l *Conn) SearchAsync(
|
||||
ctx context.Context, searchRequest *SearchRequest, bufferSize int) Response {
|
||||
|
||||
76
vendor/github.com/go-ldap/ldap/v3/whoami.go
generated
vendored
76
vendor/github.com/go-ldap/ldap/v3/whoami.go
generated
vendored
@@ -4,88 +4,20 @@ package ldap
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc4532
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
)
|
||||
|
||||
type whoAmIRequest bool
|
||||
|
||||
// WhoAmIResult is returned by the WhoAmI() call
|
||||
type WhoAmIResult struct {
|
||||
AuthzID string
|
||||
}
|
||||
|
||||
func (r whoAmIRequest) encode() (*ber.Packet, error) {
|
||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Who Am I? Extended Operation")
|
||||
request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, ControlTypeWhoAmI, "Extended Request Name: Who Am I? OID"))
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// WhoAmI returns the authzId the server thinks we are, you may pass controls
|
||||
// like a Proxied Authorization control
|
||||
func (l *Conn) WhoAmI(controls []Control) (*WhoAmIResult, 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"))
|
||||
req := whoAmIRequest(true)
|
||||
encodedWhoAmIRequest, err := req.encode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
packet.AppendChild(encodedWhoAmIRequest)
|
||||
|
||||
if len(controls) != 0 {
|
||||
packet.AppendChild(encodeControls(controls))
|
||||
}
|
||||
|
||||
l.Debug.PrintPacket(packet)
|
||||
|
||||
msgCtx, err := l.sendMessage(packet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer l.finishMessage(msgCtx)
|
||||
|
||||
result := &WhoAmIResult{}
|
||||
|
||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
||||
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)
|
||||
extendedRequest := NewExtendedRequest(ControlTypeWhoAmI, nil)
|
||||
extendedRequest.Controls = controls
|
||||
resp, err := l.Extended(extendedRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if packet == nil {
|
||||
return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
|
||||
}
|
||||
|
||||
if l.Debug {
|
||||
if err := addLDAPDescriptions(packet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ber.PrintPacket(packet)
|
||||
}
|
||||
|
||||
if packet.Children[1].Tag == ApplicationExtendedResponse {
|
||||
if err := GetLDAPError(packet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag))
|
||||
}
|
||||
|
||||
extendedResponse := packet.Children[1]
|
||||
for _, child := range extendedResponse.Children {
|
||||
if child.Tag == 11 {
|
||||
result.AuthzID = ber.DecodeString(child.Data.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return &WhoAmIResult{AuthzID: resp.Value.Data.String()}, nil
|
||||
}
|
||||
|
||||
9
vendor/modules.txt
vendored
9
vendor/modules.txt
vendored
@@ -12,9 +12,10 @@ filippo.io/edwards25519/field
|
||||
## explicit; go 1.16
|
||||
github.com/Azure/go-ansiterm
|
||||
github.com/Azure/go-ansiterm/winterm
|
||||
# github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
## explicit
|
||||
# github.com/Azure/go-ntlmssp v0.1.0
|
||||
## explicit; go 1.24
|
||||
github.com/Azure/go-ntlmssp
|
||||
github.com/Azure/go-ntlmssp/internal/md4
|
||||
# github.com/BurntSushi/toml v1.6.0
|
||||
## explicit; go 1.18
|
||||
github.com/BurntSushi/toml
|
||||
@@ -570,8 +571,8 @@ github.com/go-jose/go-jose/v4/json
|
||||
## explicit; go 1.17
|
||||
github.com/go-kit/log
|
||||
github.com/go-kit/log/level
|
||||
# github.com/go-ldap/ldap/v3 v3.4.12
|
||||
## explicit; go 1.23.0
|
||||
# github.com/go-ldap/ldap/v3 v3.4.13
|
||||
## explicit; go 1.24.0
|
||||
github.com/go-ldap/ldap/v3
|
||||
# github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3
|
||||
## explicit; go 1.14
|
||||
|
||||
Reference in New Issue
Block a user