Commit f9c2a17e authored by ale's avatar ale

Update to go-ldap v3

parent bc2d1de6
Pipeline #2240 passed with stages
in 1 minute and 15 seconds
......@@ -9,7 +9,7 @@ import (
"strings"
ldaputil "git.autistici.org/ai3/go-common/ldap"
"gopkg.in/ldap.v2"
"gopkg.in/ldap.v3"
)
type LDAPQueryConfig struct {
......
......@@ -3,7 +3,7 @@ package ldaputil
import (
"fmt"
"gopkg.in/ldap.v2"
"gopkg.in/ldap.v3"
)
// ParseScope parses a string representation of an LDAP scope into the
......
......@@ -9,7 +9,7 @@ import (
"github.com/cenkalti/backoff"
"go.opencensus.io/trace"
"gopkg.in/ldap.v2"
"gopkg.in/ldap.v3"
)
// Parameters that define the exponential backoff algorithm used.
......
// +build go1.4
package ldap
import (
"sync/atomic"
)
// For compilers that support it, we just use the underlying sync/atomic.Value
// type.
type atomicValue struct {
atomic.Value
}
// +build !go1.4
package ldap
import (
"sync"
)
// This is a helper type that emulates the use of the "sync/atomic.Value"
// struct that's available in Go 1.4 and up.
type atomicValue struct {
value interface{}
lock sync.RWMutex
}
func (av *atomicValue) Store(val interface{}) {
av.lock.Lock()
av.value = val
av.lock.Unlock()
}
func (av *atomicValue) Load() interface{} {
av.lock.RLock()
ret := av.value
av.lock.RUnlock()
return ret
}
package ldap
import (
"fmt"
"gopkg.in/asn1-ber.v1"
)
// LDAP Result Codes
const (
LDAPResultSuccess = 0
LDAPResultOperationsError = 1
LDAPResultProtocolError = 2
LDAPResultTimeLimitExceeded = 3
LDAPResultSizeLimitExceeded = 4
LDAPResultCompareFalse = 5
LDAPResultCompareTrue = 6
LDAPResultAuthMethodNotSupported = 7
LDAPResultStrongAuthRequired = 8
LDAPResultReferral = 10
LDAPResultAdminLimitExceeded = 11
LDAPResultUnavailableCriticalExtension = 12
LDAPResultConfidentialityRequired = 13
LDAPResultSaslBindInProgress = 14
LDAPResultNoSuchAttribute = 16
LDAPResultUndefinedAttributeType = 17
LDAPResultInappropriateMatching = 18
LDAPResultConstraintViolation = 19
LDAPResultAttributeOrValueExists = 20
LDAPResultInvalidAttributeSyntax = 21
LDAPResultNoSuchObject = 32
LDAPResultAliasProblem = 33
LDAPResultInvalidDNSyntax = 34
LDAPResultAliasDereferencingProblem = 36
LDAPResultInappropriateAuthentication = 48
LDAPResultInvalidCredentials = 49
LDAPResultInsufficientAccessRights = 50
LDAPResultBusy = 51
LDAPResultUnavailable = 52
LDAPResultUnwillingToPerform = 53
LDAPResultLoopDetect = 54
LDAPResultNamingViolation = 64
LDAPResultObjectClassViolation = 65
LDAPResultNotAllowedOnNonLeaf = 66
LDAPResultNotAllowedOnRDN = 67
LDAPResultEntryAlreadyExists = 68
LDAPResultObjectClassModsProhibited = 69
LDAPResultAffectsMultipleDSAs = 71
LDAPResultOther = 80
ErrorNetwork = 200
ErrorFilterCompile = 201
ErrorFilterDecompile = 202
ErrorDebugging = 203
ErrorUnexpectedMessage = 204
ErrorUnexpectedResponse = 205
)
// LDAPResultCodeMap contains string descriptions for LDAP error codes
var LDAPResultCodeMap = map[uint8]string{
LDAPResultSuccess: "Success",
LDAPResultOperationsError: "Operations Error",
LDAPResultProtocolError: "Protocol Error",
LDAPResultTimeLimitExceeded: "Time Limit Exceeded",
LDAPResultSizeLimitExceeded: "Size Limit Exceeded",
LDAPResultCompareFalse: "Compare False",
LDAPResultCompareTrue: "Compare True",
LDAPResultAuthMethodNotSupported: "Auth Method Not Supported",
LDAPResultStrongAuthRequired: "Strong Auth Required",
LDAPResultReferral: "Referral",
LDAPResultAdminLimitExceeded: "Admin Limit Exceeded",
LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension",
LDAPResultConfidentialityRequired: "Confidentiality Required",
LDAPResultSaslBindInProgress: "Sasl Bind In Progress",
LDAPResultNoSuchAttribute: "No Such Attribute",
LDAPResultUndefinedAttributeType: "Undefined Attribute Type",
LDAPResultInappropriateMatching: "Inappropriate Matching",
LDAPResultConstraintViolation: "Constraint Violation",
LDAPResultAttributeOrValueExists: "Attribute Or Value Exists",
LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax",
LDAPResultNoSuchObject: "No Such Object",
LDAPResultAliasProblem: "Alias Problem",
LDAPResultInvalidDNSyntax: "Invalid DN Syntax",
LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem",
LDAPResultInappropriateAuthentication: "Inappropriate Authentication",
LDAPResultInvalidCredentials: "Invalid Credentials",
LDAPResultInsufficientAccessRights: "Insufficient Access Rights",
LDAPResultBusy: "Busy",
LDAPResultUnavailable: "Unavailable",
LDAPResultUnwillingToPerform: "Unwilling To Perform",
LDAPResultLoopDetect: "Loop Detect",
LDAPResultNamingViolation: "Naming Violation",
LDAPResultObjectClassViolation: "Object Class Violation",
LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf",
LDAPResultNotAllowedOnRDN: "Not Allowed On RDN",
LDAPResultEntryAlreadyExists: "Entry Already Exists",
LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited",
LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs",
LDAPResultOther: "Other",
ErrorNetwork: "Network Error",
ErrorFilterCompile: "Filter Compile Error",
ErrorFilterDecompile: "Filter Decompile Error",
ErrorDebugging: "Debugging Error",
ErrorUnexpectedMessage: "Unexpected Message",
ErrorUnexpectedResponse: "Unexpected Response",
}
func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) {
if packet == nil {
return ErrorUnexpectedResponse, "Empty packet"
} else if len(packet.Children) >= 2 {
response := packet.Children[1]
if response == nil {
return ErrorUnexpectedResponse, "Empty response in packet"
}
if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 {
// Children[1].Children[2] is the diagnosticMessage which is guaranteed to exist as seen here: https://tools.ietf.org/html/rfc4511#section-4.1.9
return uint8(response.Children[0].Value.(int64)), response.Children[2].Value.(string)
}
}
return ErrorNetwork, "Invalid packet format"
}
// Error holds LDAP error information
type Error struct {
// Err is the underlying error
Err error
// ResultCode is the LDAP error code
ResultCode uint8
}
func (e *Error) Error() string {
return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error())
}
// NewError creates an LDAP error with the given code and underlying error
func NewError(resultCode uint8, err error) error {
return &Error{ResultCode: resultCode, Err: err}
}
// IsErrorWithCode returns true if the given error is an LDAP error with the given result code
func IsErrorWithCode(err error, desiredResultCode uint8) bool {
if err == nil {
return false
}
serverError, ok := err.(*Error)
if !ok {
return false
}
return serverError.ResultCode == desiredResultCode
}
# 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)
......@@ -36,7 +36,23 @@ fmt:
# Only run on go1.5+
vet:
go tool vet -atomic -bool -copylocks -nilfunc -printf -shadow -rangeloops -unreachable -unsafeptr -unusedresult .
@go tool -n vet >/dev/null 2>&1; \
if [ $$? -eq 0 ]; then \
echo "go vet" ; \
go tool vet \
-atomic \
-bool \
-copylocks \
-nilfunc \
-printf \
-shadow \
-rangeloops \
-unreachable \
-unsafeptr \
-unusedresult \
. ; \
fi ;
# https://github.com/golang/lint
# go get github.com/golang/lint/golint
......@@ -44,7 +60,7 @@ vet:
# Only run on go1.5+
lint:
@echo golint ./...
@OUTPUT=`golint ./... 2>&1`; \
@OUTPUT=`command -v golint >/dev/null 2>&1 && golint ./... 2>&1`; \
if [ "$$OUTPUT" ]; then \
echo "golint errors:"; \
echo "$$OUTPUT"; \
......
[![GoDoc](https://godoc.org/gopkg.in/ldap.v2?status.svg)](https://godoc.org/gopkg.in/ldap.v2)
[![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.
......@@ -7,11 +7,11 @@
For the latest version use:
go get gopkg.in/ldap.v2
go get gopkg.in/ldap.v3
Import the latest version with:
import "gopkg.in/ldap.v2"
import "gopkg.in/ldap.v3"
## Required Libraries:
......@@ -27,6 +27,7 @@ Import the latest version with:
- Modify Requests / Responses
- Add Requests / Responses
- Delete Requests / Responses
- Modify DN Requests / Responses
## Examples:
......
......@@ -41,6 +41,8 @@ type AddRequest struct {
DN string
// Attributes list the attributes of the new entry
Attributes []Attribute
// Controls hold optional controls to send with the request
Controls []Control
}
func (a AddRequest) encode() *ber.Packet {
......@@ -60,9 +62,10 @@ func (a *AddRequest) Attribute(attrType string, attrVals []string) {
}
// NewAddRequest returns an AddRequest for the given DN, with no attributes
func NewAddRequest(dn string) *AddRequest {
func NewAddRequest(dn string, controls []Control) *AddRequest {
return &AddRequest{
DN: dn,
DN: dn,
Controls: controls,
}
}
......@@ -72,6 +75,9 @@ func (l *Conn) Add(addRequest *AddRequest) 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(addRequest.encode())
if len(addRequest.Controls) > 0 {
packet.AppendChild(encodeControls(addRequest.Controls))
}
l.Debug.PrintPacket(packet)
......@@ -100,9 +106,9 @@ func (l *Conn) Add(addRequest *AddRequest) error {
}
if packet.Children[1].Tag == ApplicationAddResponse {
resultCode, resultDescription := getLDAPResultCode(packet)
if resultCode != 0 {
return NewError(resultCode, errors.New(resultDescription))
err := GetLDAPError(packet)
if err != nil {
return err
}
} else {
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
......
// Copyright 2011 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 ldap
import (
"errors"
"fmt"
"gopkg.in/asn1-ber.v1"
)
......@@ -18,6 +15,9 @@ type SimpleBindRequest struct {
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
......@@ -28,9 +28,10 @@ type SimpleBindResult struct {
// NewSimpleBindRequest returns a bind request
func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest {
return &SimpleBindRequest{
Username: username,
Password: password,
Controls: controls,
Username: username,
Password: password,
Controls: controls,
AllowEmptyPassword: false,
}
}
......@@ -40,17 +41,22 @@ func (bindRequest *SimpleBindRequest) encode() *ber.Packet {
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"))
request.AppendChild(encodeControls(bindRequest.Controls))
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)
......@@ -73,7 +79,7 @@ func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResu
}
if l.Debug {
if err := addLDAPDescriptions(packet); err != nil {
if err = addLDAPDescriptions(packet); err != nil {
return nil, err
}
ber.PrintPacket(packet)
......@@ -85,59 +91,45 @@ func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResu
if len(packet.Children) == 3 {
for _, child := range packet.Children[2].Children {
result.Controls = append(result.Controls, DecodeControl(child))
decodedChild, decodeErr := DecodeControl(child)
if decodeErr != nil {
return nil, fmt.Errorf("failed to decode child control: %s", decodeErr)
}
result.Controls = append(result.Controls, decodedChild)
}
}
resultCode, resultDescription := getLDAPResultCode(packet)
if resultCode != 0 {
return result, NewError(resultCode, errors.New(resultDescription))
}
return result, nil
err = GetLDAPError(packet)
return result, err
}
// Bind performs a bind with the given username and password
// 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 {
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"))
bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
bindRequest.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, username, "User Name"))
bindRequest.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, password, "Password"))
packet.AppendChild(bindRequest)
if l.Debug {
ber.PrintPacket(packet)
}
msgCtx, err := l.sendMessage(packet)
if err != nil {
return err
}
defer l.finishMessage(msgCtx)
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)
req := &SimpleBindRequest{
Username: username,
Password: password,
AllowEmptyPassword: false,
}
_, err := l.SimpleBind(req)
return err
}
resultCode, resultDescription := getLDAPResultCode(packet)
if resultCode != 0 {
return NewError(resultCode, errors.New(resultDescription))
// 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,
}
return nil
_, err := l.SimpleBind(req)
return err
}
......@@ -18,6 +18,7 @@ type Client interface {
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)
......
// Copyright 2014 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.
//
// File contains Compare functionality
//
// https://tools.ietf.org/html/rfc4511
......@@ -41,7 +37,7 @@ func (l *Conn) Compare(dn, attribute, value string) (bool, error) {
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.TypeConstructed, ber.TagOctetString, value, "AssertionValue"))
ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "AssertionValue"))
request.AppendChild(ava)
packet.AppendChild(request)
......@@ -72,14 +68,16 @@ func (l *Conn) Compare(dn, attribute, value string) (bool, error) {
}
if packet.Children[1].Tag == ApplicationCompareResponse {
resultCode, resultDescription := getLDAPResultCode(packet)
if resultCode == LDAPResultCompareTrue {
err := GetLDAPError(packet)
switch {
case IsErrorWithCode(err, LDAPResultCompareTrue):
return true, nil
} else if resultCode == LDAPResultCompareFalse {
case IsErrorWithCode(err, LDAPResultCompareFalse):
return false, nil
} else {
return false, NewError(resultCode, errors.New(resultDescription))
default:
return false, err
}
}
return false, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag)
return false, fmt.Errorf("unexpected Response: %d", packet.Children[1].Tag)
}
// Copyright 2011 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 ldap
import (
......@@ -10,6 +6,7 @@ import (
"fmt"
"log"
"net"
"net/url"
"sync"
"sync/atomic"
"time"
......@@ -30,6 +27,13 @@ const (
MessageTimeout = 4
)
const (
// DefaultLdapPort default ldap port for pure TCP connection
DefaultLdapPort = "389"
// DefaultLdapsPort default ldap port for SSL connection
DefaultLdapsPort = "636"
)
// PacketResponse contains the packet or error encountered reading a response
type PacketResponse struct {
// Packet is the packet read from the server
......@@ -81,10 +85,13 @@ const (
// Conn represents an LDAP Connection
type Conn struct {
// requestTimeout is loaded atomically
// so we need to ensure 64-bit alignment on 32-bit platforms.
requestTimeout int64
conn net.Conn
isTLS bool
closing uint32
closeErr atomicValue
closeErr atomic.Value
isStartingTLS bool
Debug debugging
chanConfirm chan struct{}
......@@ -94,7 +101,6 @@ type Conn struct {
wgClose sync.WaitGroup
outstandingRequests uint
messageMutex sync.Mutex
requestTimeout int64
}
var _ Client = &Conn{}
......@@ -121,22 +127,51 @@ func Dial(network, addr string) (*Conn, error) {
// DialTLS connects to the given address on the given network using tls.Dial
// and then returns a new Conn for the connection.
func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
dc, err := net.DialTimeout(network, addr, DefaultTimeout)
c, err := tls.DialWithDialer(&net.Dialer{Timeout: DefaultTimeout}, network, addr, config)
if err != nil {
return nil, NewError(ErrorNetwork, err)
}
c := tls.Client(dc, config)
err = c.Handshake()
if err != nil {
// Handshake error, close the established connection before we return an error
dc.Close()
return nil, NewError(ErrorNetwork, err)
}
conn := NewConn(c, true)
conn.Start()
return conn, nil
}
// DialURL connects to the given ldap URL vie TCP using tls.Dial or net.Dial if ldaps://
// or ldap:// specified as protocol. On success a new Conn for the connection
// is returned.
func DialURL(addr string) (*Conn, error) {
lurl, err := url.Parse(addr)
if err != nil {
return nil, NewError(ErrorNetwork, err)
}
host, port, err := net.SplitHostPort(lurl.Host)
if err != nil {
// we asume that error is due to missing port
host = lurl.Host
port = ""
}
switch lurl.Scheme {
case "ldap":
if port == "" {
port = DefaultLdapPort
}
return Dial("tcp", net.JoinHostPort(host, port))
case "ldaps":
if port == "" {
port = DefaultLdapsPort
}
tlsConf := &tls.Config{
ServerName: host,
}
return DialTLS("tcp", net.JoinHostPort(host, port), tlsConf)
}
return nil, NewError(ErrorNetwork, fmt.Errorf("Unknown scheme '%s'", lurl.Scheme))
}
// NewConn returns a new Conn using conn for network I/O.
func NewConn(conn net.Conn, isTLS bool) *Conn {
return &Conn{
......@@ -157,8 +192,8 @@ func (l *Conn) Start() {
l.wgClose.Add(1)
}
// isClosing returns whether or not we're currently closing.
func (l *Conn) isClosing() bool {
// IsClosing returns whether or not we're currently closing.
func (l *Conn) IsClosing() bool {
return atomic.LoadUint32(&l.closing) == 1
}
......@@ -242,30 +277,41 @@ func (l *Conn) StartTLS(config *tls.Config) error {
ber.PrintPacket(packet)
}
if resultCode, message := getLDAPResultCode(packet); resultCode == LDAPResultSuccess {
if err := GetLDAPError(packet); err == nil {
conn := tls.Client(l.conn, config)
if err := conn.Handshake(); err != nil {
if connErr := conn.Handshake(); connErr != nil {
l.Close()
return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", err))
return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", connErr))
}
l.isTLS = true
l.conn = conn
} else {
return NewError(resultCode, fmt.Errorf("ldap: cannot StartTLS (%s)", message))
return err
}
go l.reader()
return nil
}
// TLSConnectionState returns the client's TLS connection state.
// The return values are their zero values if StartTLS did
// not succeed.
func (l *Conn) TLSConnectionState() (state tls.ConnectionState, ok bool) {
tc, ok := l.conn.(*tls.Conn)
if !ok {
return
}
return tc.ConnectionState(), true
}
func (l *Conn) sendMessage(packet *ber.Packet) (*messageContext, error) {
return l.sendMessageWithFlags(packet, 0)
}
func (l *Conn) sendMessageWithFlags(packet *ber.Packet