From e7036cec025da8d9d77715a52c010fa86ff477e9 Mon Sep 17 00:00:00 2001 From: rc4 Date: Fri, 19 Jun 2026 18:47:13 -0400 Subject: [PATCH 1/5] chore(bind): cleanup lint issues in bind.go - drop duplicate encoding/hex import alias enchex - replace deprecated io/ioutil import with io - migrate deprecated go-ntlmssp ProcessChallenge/ProcessChallengeWithHash/GetDomain to NewAuthenticateMessage - cleanup switch syntax --- v3/bind.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/v3/bind.go b/v3/bind.go index 555001c6..a41e56e0 100644 --- a/v3/bind.go +++ b/v3/bind.go @@ -6,10 +6,9 @@ import ( "crypto/rand" "encoding/binary" "encoding/hex" - enchex "encoding/hex" "errors" "fmt" - "io/ioutil" + "io" "strings" "unicode/utf16" @@ -212,7 +211,7 @@ func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*Diges if child.Data == nil { return result, GetLDAPError(packet) } - data, _ := ioutil.ReadAll(child.Data) + data, _ := io.ReadAll(child.Data) params, err = parseParams(string(data)) if err != nil { return result, fmt.Errorf("parsing digest-challenge: %s", err) @@ -356,7 +355,7 @@ func computeResponse(params map[string]string, uri, username, password string) ( if err != nil { return "", err } - cnonce := enchex.EncodeToString(rb) + cnonce := hex.EncodeToString(rb) x := username + ":" + params["realm"] + ":" + password y := md5Hash([]byte(x)) @@ -367,8 +366,8 @@ func computeResponse(params map[string]string, uri, username, password string) ( } a2 := bytes.NewBuffer([]byte("AUTHENTICATE")) a2.WriteString(":" + uri) - ha1 := enchex.EncodeToString(md5Hash(a1.Bytes())) - ha2 := enchex.EncodeToString(md5Hash(a2.Bytes())) + ha1 := hex.EncodeToString(md5Hash(a1.Bytes())) + ha2 := hex.EncodeToString(md5Hash(a2.Bytes())) kd := ha1 kd += ":" + params["nonce"] @@ -376,7 +375,7 @@ func computeResponse(params map[string]string, uri, username, password string) ( kd += ":" + cnonce kd += ":" + qop kd += ":" + ha2 - resp := enchex.EncodeToString(md5Hash([]byte(kd))) + resp := hex.EncodeToString(md5Hash([]byte(kd))) return fmt.Sprintf( `username="%s",realm="%s",nonce="%s",cnonce="%s",nc=00000001,qop=%s,digest-uri="%s",response=%s`, quotedStringEscape(username), @@ -485,8 +484,8 @@ func (req *NTLMBindRequest) appendTo(envelope *ber.Packet) (err error) { var negMessage []byte // generate an NTLMSSP Negotiation message for the specified domain (it can be blank) - switch { - case req.Negotiator == nil: + switch req.Negotiator { + case nil: negMessage, err = ntlmssp.NewNegotiateMessage(req.Domain, "") if err != nil { return fmt.Errorf("create NTLM negotiate message: %s", err) @@ -598,11 +597,12 @@ func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindRes case ntlmBindRequest.Hash == "" && ntlmBindRequest.Password == "" && !ntlmBindRequest.AllowEmptyPassword: err = fmt.Errorf("need a password or hash to generate reply") case ntlmBindRequest.Negotiator == nil && ntlmBindRequest.Hash != "": - responseMessage, err = ntlmssp.ProcessChallengeWithHash(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Hash) + responseMessage, err = ntlmssp.NewAuthenticateMessage(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Hash, &ntlmssp.AuthenticateMessageOptions{ + PasswordHashed: true, + }) case ntlmBindRequest.Negotiator == nil && (ntlmBindRequest.Password != "" || ntlmBindRequest.AllowEmptyPassword): // generate a response message to the challenge with the given Username/Password if password is provided - _, _, domainNeeded := ntlmssp.GetDomain(ntlmBindRequest.Username) - responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password, domainNeeded) + responseMessage, err = ntlmssp.NewAuthenticateMessage(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password, nil) default: hash := ntlmBindRequest.Hash if len(hash) == 0 { @@ -722,7 +722,7 @@ func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) er return l.GSSAPIBindRequestWithAPOptions(client, req, []int{}) } -// GSSAPIBindRequest performs the GSSAPI SASL bind using the provided GSSAPI client. +// GSSAPIBindRequestWithAPOptions performs the GSSAPI SASL bind using the provided GSSAPI client and AP options. func (l *Conn) GSSAPIBindRequestWithAPOptions(client GSSAPIClient, req *GSSAPIBindRequest, APOptions []int) error { //nolint:errcheck defer client.DeleteSecContext() @@ -828,7 +828,7 @@ RESP: if referral.ClassType != ber.ClassContext || referral.Tag != ber.TagObjectDescriptor { break RESP } - return ioutil.ReadAll(referral.Data) + return io.ReadAll(referral.Data) } // Optional: //if len(protocolOp.Children) == 4 { From 995995b7c5fafd76973ae1225fd68b5e8ec427b5 Mon Sep 17 00:00:00 2001 From: rc4 Date: Fri, 19 Jun 2026 18:56:00 -0400 Subject: [PATCH 2/5] chore(control): resolve staticcheck/lint issues in control.go - simplify pkt.Identifier.Tag to pkt.Tag via embedded promotion - make ControlServerSideSortingResult receiver names consistent - fix doc comment form on some types - cleanup switch syntax --- v3/control.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/v3/control.go b/v3/control.go index ddb923e4..0fec4baa 100644 --- a/v3/control.go +++ b/v3/control.go @@ -657,23 +657,25 @@ func DecodeControl(packet *ber.Packet) (Control, error) { sequence := value.Children[0] for _, child := range sequence.Children { - if child.Tag == 0 { + switch child.Tag { + case 0: // Warning warningPacket := child.Children[0] val, err := ber.ParseInt64(warningPacket.Data.Bytes()) if err != nil { return nil, fmt.Errorf("failed to decode data bytes: %s", err) } - if warningPacket.Tag == 0 { + switch warningPacket.Tag { + case 0: // timeBeforeExpiration c.Expire = val warningPacket.Value = c.Expire - } else if warningPacket.Tag == 1 { + case 1: // graceAuthNsRemaining c.Grace = val warningPacket.Value = c.Grace } - } else if child.Tag == 1 { + case 1: // Error bs := child.Data.Bytes() if len(bs) != 1 || bs[0] > 8 { @@ -1111,7 +1113,7 @@ type ControlServerSideSortingResult struct { // AttributeType string } -func (control *ControlServerSideSortingResult) GetControlType() string { +func (c *ControlServerSideSortingResult) GetControlType() string { return ControlTypeServerSideSortingResult } @@ -1133,7 +1135,7 @@ func (c *ControlServerSideSortingResult) String() string { ) } -// Mode for ControlTypeSyncRequest +// ControlSyncRequestMode is the mode for ControlTypeSyncRequest type ControlSyncRequestMode int64 const ( @@ -1207,7 +1209,7 @@ func (c *ControlSyncRequest) String() string { ) } -// State for ControlSyncState +// ControlSyncStateState is the state for ControlSyncState type ControlSyncStateState int64 const ( @@ -1330,7 +1332,7 @@ func (c *ControlSyncDone) String() string { ) } -// Tag For ControlSyncInfo +// ControlSyncInfoValue is the tag for ControlSyncInfo type ControlSyncInfoValue uint64 const ( @@ -1418,7 +1420,7 @@ func NewControlSyncInfo(pkt *ber.Packet) (*ControlSyncInfo, error) { syncUUIDs []uuid.UUID ) c := &ControlSyncInfo{Criticality: false} - switch ControlSyncInfoValue(pkt.Identifier.Tag) { + switch ControlSyncInfoValue(pkt.Tag) { case SyncInfoNewcookie: c.Value = SyncInfoNewcookie c.NewCookie = &ControlSyncInfoNewCookie{ @@ -1482,7 +1484,7 @@ func NewControlSyncInfo(pkt *ber.Packet) (*ControlSyncInfo, error) { SyncUUIDs: syncUUIDs, } default: - return nil, fmt.Errorf("unknown sync info value: %d", pkt.Identifier.Tag) + return nil, fmt.Errorf("unknown sync info value: %d", pkt.Tag) } return c, nil } From 8cb2b4b390730b2819f57cf37a44ee9355b96ba4 Mon Sep 17 00:00:00 2001 From: rc4 Date: Fri, 19 Jun 2026 19:32:49 -0400 Subject: [PATCH 3/5] chore: fix staticcheck/lint findings - ldap.go: replace deprecated io/ioutil with os.ReadFile; tidy up switch statement syntax - search.go: use any in lieu of interface{}, reflect.Ptr -> reflect.Pointer, tidy up doc comment - debug.go: use any in lieu of interface{} - gssapi/client.go: drop redundant embedded Client selector --- v3/debug.go | 2 +- v3/gssapi/client.go | 4 ++-- v3/ldap.go | 13 +++++++------ v3/search.go | 8 ++++---- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/v3/debug.go b/v3/debug.go index 6f89b4a0..2f852e8a 100644 --- a/v3/debug.go +++ b/v3/debug.go @@ -14,7 +14,7 @@ func (debug *debugging) Enable(b bool) { } // Printf writes debug output. -func (debug debugging) Printf(format string, args ...interface{}) { +func (debug debugging) Printf(format string, args ...any) { if debug { logger.Printf(format, args...) } diff --git a/v3/gssapi/client.go b/v3/gssapi/client.go index 4bbca0cd..bc81fa8f 100644 --- a/v3/gssapi/client.go +++ b/v3/gssapi/client.go @@ -89,7 +89,7 @@ func NewClientFromCCache(ccachePath, krb5confPath string, settings ...func(*clie // Close deletes any established secure context and closes the client. func (client *Client) Close() error { - client.Client.Destroy() + client.Destroy() return nil } @@ -115,7 +115,7 @@ func (client *Client) InitSecContextWithOptions(target string, input []byte, APO switch input { case nil: - tkt, ekey, err := client.Client.GetServiceTicket(target) + tkt, ekey, err := client.GetServiceTicket(target) if err != nil { return nil, false, err } diff --git a/v3/ldap.go b/v3/ldap.go index 802a529e..f5a7062b 100644 --- a/v3/ldap.go +++ b/v3/ldap.go @@ -2,7 +2,6 @@ package ldap import ( "fmt" - "io/ioutil" "log" "os" "strings" @@ -231,23 +230,25 @@ func addControlDescriptions(packet *ber.Packet) error { } sequence := value.Children[0] for _, child := range sequence.Children { - if child.Tag == 0 { + switch child.Tag { + case 0: // Warning warningPacket := child.Children[0] val, err := ber.ParseInt64(warningPacket.Data.Bytes()) if err != nil { return fmt.Errorf("failed to decode data bytes: %s", err) } - if warningPacket.Tag == 0 { + switch warningPacket.Tag { + case 0: // timeBeforeExpiration value.Description += " (TimeBeforeExpiration)" warningPacket.Value = val - } else if warningPacket.Tag == 1 { + case 1: // graceAuthNsRemaining value.Description += " (GraceAuthNsRemaining)" warningPacket.Value = val } - } else if child.Tag == 1 { + case 1: // Error bs := child.Data.Bytes() if len(bs) != 1 || bs[0] > 8 { @@ -297,7 +298,7 @@ func addDefaultLDAPResponseDescriptions(packet *ber.Packet) error { // DebugBinaryFile reads and prints packets from the given filename func DebugBinaryFile(fileName string) error { - file, err := ioutil.ReadFile(fileName) + file, err := os.ReadFile(fileName) if err != nil { return NewError(ErrorDebugging, err) } diff --git a/v3/search.go b/v3/search.go index e1c684e1..12c82ba1 100644 --- a/v3/search.go +++ b/v3/search.go @@ -245,7 +245,7 @@ func readTag(f reflect.StructField) (string, bool) { // if err := result.Unmarshal(&user); err != nil { // // ... // } -func (e *Entry) Unmarshal(i interface{}) (err error) { +func (e *Entry) Unmarshal(i any) (err error) { return e.UnmarshalFunc(i, func(entry *Entry, ft reflect.StructField, fv reflect.Value) error { // omitempty can be safely discarded, as it's not needed when unmarshalling fieldTag, _ := readTag(ft) @@ -307,10 +307,10 @@ func (e *Entry) Unmarshal(i interface{}) (err error) { // UnmarshalFunc allows you to define a custom unmarshaler to parse an Entry values. // A custom unmarshaler can be found in the Unmarshal function or in the test files. -func (e *Entry) UnmarshalFunc(i interface{}, +func (e *Entry) UnmarshalFunc(i any, fn func(entry *Entry, fieldType reflect.StructField, fieldValue reflect.Value) error) error { // Make sure it's a ptr - if vo := reflect.ValueOf(i).Kind(); vo != reflect.Ptr { + if vo := reflect.ValueOf(i).Kind(); vo != reflect.Pointer { return fmt.Errorf("ldap: cannot use %s, expected pointer to a struct", vo) } @@ -713,7 +713,7 @@ func (l *Conn) DirSync( return searchResult, nil } -// DirSyncDirSyncAsync performs a search request and returns all search results +// DirSyncAsync performs a search request and returns all search results // asynchronously. This is efficient when the server returns lots of entries. func (l *Conn) DirSyncAsync( ctx context.Context, searchRequest *SearchRequest, bufferSize int, From c09fc7c9c8e27ad8c75428575af027148ae75832 Mon Sep 17 00:00:00 2001 From: rc4 Date: Fri, 19 Jun 2026 19:37:45 -0400 Subject: [PATCH 4/5] chore: fix staticcheck/lint findings in tests - examples_test.go: replace deprecated io/ioutil with os.ReadFile - search_test.go: use range syntax instaed of manual loop - error_test.go: rename panic_data to panicData per ST1003, drop redundant tt copy (no longer needed since go1.22) --- v3/error_test.go | 5 ++--- v3/examples_test.go | 4 ++-- v3/search_test.go | 7 +------ 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/v3/error_test.go b/v3/error_test.go index fd8fd5c4..abfa534c 100644 --- a/v3/error_test.go +++ b/v3/error_test.go @@ -80,7 +80,6 @@ func TestWrappedError(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() actual := IsErrorAnyOf(tt.err, tt.codes...) @@ -212,8 +211,8 @@ func generateGetLDAPErrorCorpus() map[string]testCorpusErrorEntry { // Test that if the matchedDN is nil, we get an appropriate error instead of a panic. // Panic message would be "interface conversion: interface {} is nil, not string" - panic_data := []byte("07A\x010\x7f\xff00\x02\x010D\"0000000000000000000000000000000000D\x010A\x010A\x010") - packet, err := ber.ReadPacket(bytes.NewReader(panic_data)) + panicData := []byte("07A\x010\x7f\xff00\x02\x010D\"0000000000000000000000000000000000D\x010A\x010A\x010") + packet, err := ber.ReadPacket(bytes.NewReader(panicData)) if err != nil { panic(fmt.Sprintf("failed to read packet for panic test: %s", err)) } diff --git a/v3/examples_test.go b/v3/examples_test.go index e4e980d8..965a2724 100644 --- a/v3/examples_test.go +++ b/v3/examples_test.go @@ -5,8 +5,8 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "io/ioutil" "log" + "os" "time" ) @@ -522,7 +522,7 @@ func ExampleConn_ExternalBind() { } // Load CA chain - caCert, err := ioutil.ReadFile(ldapCAchain) + caCert, err := os.ReadFile(ldapCAchain) if err != nil { log.Fatal(err) } diff --git a/v3/search_test.go b/v3/search_test.go index 381284ef..86708ab9 100644 --- a/v3/search_test.go +++ b/v3/search_test.go @@ -21,16 +21,11 @@ func TestNewEntry(t *testing.T) { } executedEntry := NewEntry(dn, attributes) - iteration := 0 - for { - if iteration == 100 { - break - } + for range 100 { testEntry := NewEntry(dn, attributes) if !reflect.DeepEqual(executedEntry, testEntry) { t.Fatalf("subsequent calls to NewEntry did not yield the same result:\n\texpected:\n\t%v\n\tgot:\n\t%v\n", executedEntry, testEntry) } - iteration = iteration + 1 } } From 58ad77ed2807631220bd58c0b517f4fa52030515 Mon Sep 17 00:00:00 2001 From: rc4 Date: Fri, 19 Jun 2026 20:03:58 -0400 Subject: [PATCH 5/5] chore: make capitalized error strings lowercase (ST1005) --- v3/conn.go | 2 +- v3/error.go | 10 +++++----- v3/error_test.go | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/v3/conn.go b/v3/conn.go index 3c3e67e5..152b0319 100644 --- a/v3/conn.go +++ b/v3/conn.go @@ -204,7 +204,7 @@ func (dc *DialContext) dial(u *url.URL) (net.Conn, error) { return tls.DialWithDialer(dc.dialer, "tcp", net.JoinHostPort(host, port), dc.tlsConfig) } - return nil, fmt.Errorf("Unknown scheme '%s'", u.Scheme) + return nil, fmt.Errorf("unknown scheme '%s'", u.Scheme) } // Dial connects to the given address on the given network using net.Dial diff --git a/v3/error.go b/v3/error.go index 1cf09c4f..87e3e3a3 100644 --- a/v3/error.go +++ b/v3/error.go @@ -200,18 +200,18 @@ func (e *Error) Unwrap() error { return e.Err } // This function returns nil if resultCode in the LDAPResult sequence is success(0). func GetLDAPError(packet *ber.Packet) error { if packet == nil { - return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty packet")} + return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("empty packet")} } if len(packet.Children) >= 2 { response := packet.Children[1] if response == nil { - return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("Empty response in packet"), Packet: packet} + return &Error{ResultCode: ErrorUnexpectedResponse, Err: fmt.Errorf("empty response in packet"), Packet: packet} } if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 { if ber.Type(response.Children[0].Tag) == ber.Type(ber.TagInteger) || ber.Type(response.Children[0].Tag) == ber.Type(ber.TagEnumerated) { if response.Children[0].Value == nil { - return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("Invalid result code in packet"), Packet: packet} + return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("invalid result code in packet"), Packet: packet} } resultCode := uint16(response.Children[0].Value.(int64)) @@ -222,7 +222,7 @@ 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: ErrorNetwork, Err: fmt.Errorf("invalid matchedDN in packet"), Packet: packet} } return &Error{ ResultCode: resultCode, @@ -235,7 +235,7 @@ func GetLDAPError(packet *ber.Packet) error { } } - return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("Invalid packet format"), Packet: packet} + return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("invalid packet format"), Packet: packet} } // NewError creates an LDAP error with the given code and underlying error diff --git a/v3/error_test.go b/v3/error_test.go index abfa534c..551dfb04 100644 --- a/v3/error_test.go +++ b/v3/error_test.go @@ -167,7 +167,7 @@ func generateGetLDAPErrorCorpus() map[string]testCorpusErrorEntry { corpus["unexpected ordering"] = testCorpusErrorEntry{ packet: packet, expectedResultCode: ErrorNetwork, - expectedMessage: "Invalid packet format", + expectedMessage: "invalid packet format", shouldError: true, } @@ -175,7 +175,7 @@ func generateGetLDAPErrorCorpus() map[string]testCorpusErrorEntry { corpus["nil packet"] = testCorpusErrorEntry{ packet: nil, expectedResultCode: ErrorUnexpectedResponse, - expectedMessage: "Empty packet", + expectedMessage: "empty packet", shouldError: true, } @@ -188,13 +188,13 @@ func generateGetLDAPErrorCorpus() map[string]testCorpusErrorEntry { corpus["nil first child"] = testCorpusErrorEntry{ packet: packet, expectedResultCode: ErrorUnexpectedResponse, - expectedMessage: "Empty response in packet", + expectedMessage: "empty response in packet", shouldError: true, } // Test that if the result code is nil, we get an appropriate error instead of a panic. // Panic message would be "interface conversion: interface {} is nil, not int64" - diagnosticMessage = "Invalid result code in packet" + diagnosticMessage = "invalid result code in packet" bindResponse = ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindResponse, nil, "Bind Response") bindResponse.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, nil, "resultCode")) bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "dc=example,dc=org", "matchedDN")) @@ -219,7 +219,7 @@ func generateGetLDAPErrorCorpus() map[string]testCorpusErrorEntry { corpus["panic data"] = testCorpusErrorEntry{ packet: packet, expectedResultCode: ErrorNetwork, - expectedMessage: "Invalid matchedDN in packet", + expectedMessage: "invalid matchedDN in packet", shouldError: true, }