The BIND operation
##################

As specified in RFC4511 the **Bind** operation is the "authenticate" operation. It (and the Unbind operation as well) has
this name for historical reason.

When you open a connection to an LDAP server you're in an **anonymous** connection state. What this exactly means
is defined by the server implementation, not by the protocol. Think of this as a public access to the server data
(even if what public data mean is still a server matter). In ldap3 you establish the connection to the server
with the ``open()`` method of the Connection object. The ``bind()`` method will open the connection if not already open.

The Bind operation allows credentials to be exchanged between the client and server to establish a new
authorization state.

The Bind request typically specifies the desired authentication identity. Some Bind mechanisms also allow the client
to specify the authorization identity. If the authorization identity is not specified, the server derives it from the
authentication identity in an implementation-specific manner.

If you want to provide authentication information you must use the Bind operation to specify an identity to use to
access the data. Keep in mind that either the authentication details than the authorization details
are a local server matter. The LDAP protocol doesn't specify how the identity must be stored on the server nor how the
authorization ACLs are specified.

The Bind operation specify 4 different methods to authenticate to the server, as specified in RFC4513:

* Simple Bind: you provide user credentials by the means of a username (in a dn form) and a password.

* Anonymous Bind: the user and password are passed as empty strings.

* Unauthenticated simple Bind: you pass a username without a password. This method, even if specified in the protocol,
  should not be used because is higly insecure and should be forbidden by the server. It was used in the past for tracing purpose.

* SASL (Simple Authentication and Security Layer): this defines multiple mechanisms that each server can provide to allow access to the server.
  Before trying a mechanism you should check that the server supports it. The LDAP server publish its allowed SASL mechanism in the DSE information
  that can be read anonymously with the ``get_info=ALL`` parameter of the Server object.

The Bind method returns True if the bind is successful, False if something goes wrong while binding. In this case you
can inspect the ``result`` attribute of the Connection object to get the error description.

Simple Bind
-----------

You perform a Simple Bind operation as in the following example (using the default synchronous strategy)::

    # import class and constants
    from ldap3 import Server, Connection, ALL

    # define the server
    s = Server('servername', get_info=ALL)  # define an unsecure LDAP server, requesting info on DSE and schema

    # define the connection
    c = Connection(s, user='user_dn', password='user_password')

    # perform the Bind operation
    if not c.bind():
        print('error in bind', c.result)


The server and the connection are created with the default parameters::

    s = Server(host='servername', port=389, use_ssl=False, get_info='ALL')
    c = Connection(s, user='user_dn', password='user_password', auto_bind='NONE', version=3, authentication='SIMPLE', \
    client_strategy='SYNC', auto_referrals=True, check_names=True, read_only=False, lazy=False, raise_exceptions=False)


Refer to the Server and Connection docs for info about the default parameters.


Anonymous Bind
--------------

Anonymous bind performs a simple bind with the user name and the user password set to empty strings. The ldap3 library has
a specific authentication option to do that::

    # import class and constants
    from ldap3 import Server, Connection, ALL

    # define the server
    s = Server('servername', get_info=ALL)  # define an unsecure LDAP server, requesting info on DSE and schema

    # define the connection
    c = Connection(s)  # define an ANONYMOUS connection

    # perform the Bind operation
    if not c.bind():
        print('error in bind', c.result)


The server and the connection are created with the default parameters::

    s = Server(host='servername', port=389, use_ssl=False, get_info='ALL')
    c = Connection(s, auto_bind='NONE', version=3, authentication='ANONYMOUS', client_strategy='SYNC', auto_referrals=True, \
    check_names=True, read_only=False, lazy=False, raise_exceptions=False)

To use SSL basic authentication change the server definition to::

    s = Server('servername', use_ssl=True, get_info=ALL)  # define a secure LDAP server on the default 636 port


StartTLS
--------

If you want to raise the transport layer security to an encrypted state you can perform the *StartTLS* extended operation. With this
mechanism you can wrap the plain socket in an SSL encrypted socket::

    c.start_tls()


From now on the communication transport is encrypted. You should properly configure the Server object addind a Tls object
with the relevant ssl configuration::


    t = Tls(local_private_key_file='client_private_key.pem', local_certificate_file='client_cert.pem', validate=ssl.CERT_REQUIRED, version=ssl.PROTOCOL_TLSv1, ca_certs_file='ca_certs.b64')
    s = Server('servername', tls=t, get_info=ALL)


Please refer to the SSLTLS section for more information.


SASL
----

Three SASL mechanisms are currently implemented in the ldap3 library: EXTERNAL, DIGEST-MD5, GSSAPI (Kerberos, via the gssapi package) and PLAIN. DIGEST-MD5 is
implemented even if it is **deprecated** and moved to historic (RFC6331, July 2011) because it is **"insecure and unsuitable for use in protocols"**
(as stated by the RFC).

To query the SASL mechanism available on the server you must read the information published by the server. The ldap3 library
has a convenient way to do that::

    from ldap3 import Server, Connection, ALL
    s = Server('servername', get_info=ALL)  # define an unsecure LDAP server, requesting info on DSE and schema
    c = Connection(s)
    c.open()  # establish connection without performing any bind (equivalent to ANONYMOUS bind)
    print(s.info.supported_sasl_mechanisms)

Print out a list of the SASL mechanism supported by the server::

    ['EXTERNAL', 'DIGEST-MD5', 'GSSAPI']


External
^^^^^^^^

You can use the EXTERNAL mechanism when you're on a secure (TLS) channel. You can provide an authorization identity string in ``sasl_credentials`` or let the
server trust the credential provided when establishing the secure channel::

    from ldap3 import Server, Connection, Tls, SASL,EXTERNAL
    tls = Tls(local_private_key_file = 'key.pem', local_certificate_file = 'cert.pem', validate = ssl.CERT_REQUIRED, version = ssl.PROTOCOL_TLSv1,
              ca_certs_file = 'cacert.b64')
    server = Server(host = test_server, port = test_port_ssl, use_ssl = True, tls = tls)
    c = Connection(server, auto_bind = True, version = 3, client_strategy = test_strategy, authentication = SASL,
                             sasl_mechanism = EXTERNAL, sasl_credentials = 'username')

Digest-MD5
^^^^^^^^^^

To use the DIGEST-MD5 mechanism you must pass a 4-value or 5-value tuple as sasl_credentials: (realm, user, password, authz_id, enable_signing). You can pass None
for 'realm', 'authz_id' and 'enable_signing' if not used::

    from ldap3 import Server, Connection, SASL, DIGEST_MD5
    server = Server(host = test_server, port = test_port)
    c = Connection(server, auto_bind = True, version = 3, client_strategy = test_strategy, authentication = SASL,
                             sasl_mechanism = DIGEST_MD5, sasl_credentials = (None, 'username', 'password', None, 'sign'))

Username is not required to be an LDAP entry, but it can be any identifier recognized by the server (i.e. email, principal, ...). If
you pass None as 'realm' the default realm of the LDAP server will be used.

``enable_signing`` is an optional argument, which is only relevant for Digest-MD5 authentication. This argument enable or disable signing
(Integrity protection) when performing LDAP queries.
LDAP signing is a way to prevent replay attacks without encrypting the LDAP traffic. Microsoft publicly recommend to enforce LDAP signing when talking to
an Active Directory server : https://support.microsoft.com/en-us/help/4520412/2020-ldap-channel-binding-and-ldap-signing-requirements-for-windows

* When ``enable_signing`` is set to 'sign', LDAP requests are signed and signature of LDAP responses is verified.
* When ``enable_signing`` is set to any other value or not set, LDAP requests are not signed.

Also, DIGEST-MD5 authentication with encryption in addition to the integrity protection (``qop=auth-conf``) is not yet supported by ldap3.

**Using DIGEST-MD5 without LDAP signing is considered deprecated and should not be used.**


.. _sasl-kerberos:

Kerberos
^^^^^^^^

Kerberos authentication uses the ``gssapi`` package. You must install it and configure your Kerberos environment to use the GSSAPI mechanism::

    from ldap3 import Server, Connection, Tls, SASL, KERBEROS
    import ssl
    tls = Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1_2)
    server = Server('<servername>', use_ssl=True, tls=tls)
    c = Connection(
        server, authentication=SASL, sasl_mechanism=KERBEROS)
    c.bind()
    print(c.extend.standard.who_am_i())

You can specify which Kerberos client principal should be used with the ``user`` parameter when declaring the Connection::

    c = Connection(
        server, user='ldap-client/client.example.com',
        authentication=SASL, sasl_mechanism=KERBEROS)

By default the library attempts to bind against the service principal for the domain you attempted to connect to.
If your target LDAP service uses a round-robin DNS, it's likely that the hostname you connect to won't match. In this case,
you can either specify a hostname explicitly as the first element of the ``sasl_credentials`` connection parameter,
or pass an appropriate value of the ``ReverseDnsSetting`` enum as the first element to do a reverse DNS lookup::

    # Override server hostname for authentication
    c = Connection(
        server, sasl_credentials=('ldap-3.example.com',),
        authentication=SASL, sasl_mechanism=KERBEROS)

    # Perform a reverse DNS lookup to determine the hostname to authenticate against regardless of server specification.
    c = Connection(server, sasl_credentials=(ReverseDnsSetting.REQUIRE_RESOLVE_ALL_ADDRESSES,), authentication=SASL, sasl_mechanism=KERBEROS)

    # Only perform a reverse DNS lookup to determine the hostname to authenticate against if the server's host is
    # specified as an IP address
    c = Connection(server, sasl_credentials=(ReverseDnsSetting.REQUIRE_RESOLVE_IP_ADDRESSES_ONLY,), authentication=SASL, sasl_mechanism=KERBEROS)

    # Perform a reverse DNS lookup to determine the hostname to authenticate against, but if that lookup fails, proceed
    # and attempt to use the server host as is.
    # This is useful when working with serverpools where some servers are resolvable through reverse dns and need it,
    # while other servers are not resolvable and do not need it.
    c = Connection(server, sasl_credentials=(ReverseDnsSetting.OPTIONAL_RESOLVE_ALL_ADDRESSES,), authentication=SASL, sasl_mechanism=KERBEROS)


.. note::
   `ldap3` does not currently support any SASL data security layers, only authentication.
   
   If your server requries a string Security Strength Factor (SSF), you may receive
   an ``LDAPStrongerAuthRequiredResult`` error when binding, e.g.:
   
       SASL:[GSSAPI]: Sign or Seal are required.
  

Plain
^^^^^
The PLAIN SASL mechanism sends data in clear text, so it must rely on other means of securing the connection between the client and the LDAP server.
As stated in RFC4616 the PLAIN mechanism should not be used without adequate data security protection as this mechanism affords no integrity or confidentiality
protections itself. The mechanism is intended to be used with data security protections provided by application-layer protocol,
generally through its use of Transport Layer Security (TLS) services.

To use the PLAIN mechanism you must pass a 3-value tuple as sasl_credentials: (authorization_id, authentication_id, password). You can pass None
for authorization_id if it is not used::

    from ldap3 import Server, Connection, SASL, PLAIN
    server = Server(host = test_server, port = test_port, use_ssl=True)
    c = Connection(server, auto_bind=True, authentication=SASL, sasl_mechanism=PLAIN, sasl_credentials=(None, 'username', 'password'))

NTLM
----

The ldap3 library supports an additional method to bind to Active Directory servers via the NTLM method::

    # import class and constants
    from ldap3 import Server, Connection, SIMPLE, SYNC, ALL, SASL, NTLM

    # define the server and the connection
    s = Server('servername', get_info=ALL)
    c = Connection(s, user="AUTHTEST\\Administrator", password="password", authentication=NTLM)
    # perform the Bind operation
    if not c.bind():
        print('error in bind', c.result)

This authentication method is specific for Active Directory and uses a proprietary authentication protocol named SICILY
that breaks the LDAP RFC but can be used to access AD.

When binding via NTLM, it is also possible to authenticate with an LM:NTLM hash rather than a password::

    c = Connection(s, user="AUTHTEST\\Administrator", password="E52CAC67419A9A224A3B108F3FA6CB6D:8846F7EAEE8FB117AD06BDD830B7586C", authentication=NTLM)

LDAPI (LDAP over IPC)
---------------------

If your LDAP server provides a UNIX socket connection you can use the **ldapi:** (*Interprocess Communication*) scheme to access it from the
same machine::

    >>> # accessing OpenLDAP server in a root user session
    >>> s = Server('ldapi:///var/run/slapd/ldapi')
    >>> c = Connection(s, authentication=SASL, sasl_mechanism=EXTERNAL, sasl_credentials='')
    >>> c.bind()
    True
    >>> c.extend.standard.who_am_i()
    dn:cn=config

Using the SASL *EXTERNAL* mechanism allows you to provide to the server the credentials of the logged user.

While accessing your LDAP server via a UNIX socket you can perform any usual LDAP operation. This should be faster than using a TCP connection.
You don't need to use SSL when connecting via a socket because all the communication is in the server memory and is not exposed on the wire.


Bind as a different user while the Connection is open
-----------------------------------------------------

LDAP protocol allows to bind as a different user while the connection is open. In this case you can use the **rebind()** method
that let you change the user and the authentication method while the connection is open::

    # import class and constants
    from ldap3 import Server, Connection, ALL, LDAPBindError

    # define the server
    s = Server('servername', get_info=ALL)  # define an unsecure LDAP server, requesting info on DSE and schema

    # define the connection
    c = Connection(s, user='user_dn', password='user_password')

    # perform the Bind operation
    if not c.bind():
        print('error in bind', c.result)

    # Bind again with another user
    if not c.rebind(user='different_user_dn', password='different_user_password')
        print('error in rebind', c.result)

If credentials are invalid or the server doesn't allow you to rebind the server *could* abruptly close the connection.
This condition is checked by the ``rebind()`` method and an LDAPBindError exception will be raised if caught.

If you want an exception raised when credentials are invalid you must use the ``raise_exceptions=True`` parameter in the Connection() definition. Keep
in mind that network errors always raise an exception, even if raise_exceptions is set to False.

Extended logging
----------------
To get an idea of what's happening when you perform a Simple Bind operation using the StartTLS security feature this is
the extended log from a session to an OpenLdap server from a Windows client with dual stack IP::

    # Initialization:

    INFO:ldap3:ldap3 library initialized - logging emitted with loglevel set to DEBUG - available detail levels are: OFF, ERROR, BASIC, PROTOCOL, NETWORK, EXTENDED
    DEBUG:ldap3:ERROR:detail level set to EXTENDED
    DEBUG:ldap3:BASIC:instantiated Server: <Server(host='openldap', port=389, use_ssl=False, get_info='NO_INFO')>
    DEBUG:ldap3:BASIC:instantiated Usage object
    DEBUG:ldap3:BASIC:instantiated <SyncStrategy>: <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - closed - <no socket> - tls not started - not listening - No strategy - async - real DSA - not pooled - cannot stream output>
    DEBUG:ldap3:BASIC:instantiated Connection: <Connection(server=Server(host='openldap', port=389, use_ssl=False, get_info='NO_INFO'), user='cn=admin,o=test', password='<stripped 8 characters of sensitive data>', auto_bind='NONE', version=3, authentication='SIMPLE', client_strategy='SYNC', auto_referrals=True, check_names=True, collect_usage=True, read_only=False, lazy=False, raise_exceptions=False)>
    DEBUG:ldap3:NETWORK:opening connection for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - closed - <no socket> - tls not started - not listening - SyncStrategy>
    DEBUG:ldap3:BASIC:reset usage metrics
    DEBUG:ldap3:BASIC:start collecting usage metrics
    DEBUG:ldap3:BASIC:address for <ldap://openldap:389 - cleartext> resolved as <[<AddressFamily.AF_INET6: 23>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('fe80::215:5dff:fe8f:2f0d%20', 389, 0, 20)]>
    DEBUG:ldap3:BASIC:address for <ldap://openldap:389 - cleartext> resolved as <[<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('192.168.137.104', 389)]>
    DEBUG:ldap3:BASIC:obtained candidate address for <ldap://openldap:389 - cleartext>: <[<AddressFamily.AF_INET6: 23>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('fe80::215:5dff:fe8f:2f0d%20', 389, 0, 20)]> with mode IP_V6_PREFERRED
    DEBUG:ldap3:BASIC:obtained candidate address for <ldap://openldap:389 - cleartext>: <[<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('192.168.137.104', 389)]> with mode IP_V6_PREFERRED


    # Opening the connection (trying IPv6 then IPv4):

    DEBUG:ldap3:BASIC:try to open candidate address [<AddressFamily.AF_INET6: 23>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('fe80::215:5dff:fe8f:2f0d%20', 389, 0, 20)]
    DEBUG:ldap3:ERROR:<socket connection error: [WinError 10061] No connection could be made because the target machine actively refused it.> for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - closed - <local: [::]:49610 - remote: [None]:None> - tls not started - not listening - SyncStrategy>
    DEBUG:ldap3:BASIC:try to open candidate address [<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('192.168.137.104', 389)]
    DEBUG:ldap3:NETWORK:connection open for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:BASIC:refreshing server info for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:BASIC:start START TLS operation via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:BASIC:instantiated Tls: <Tls(validate=0)>


    # Starting TLS - wrapping the socket in an ssl socket:

    DEBUG:ldap3:BASIC:starting tls for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:BASIC:start EXTENDED operation via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:PROTOCOL:EXTENDED request <{'name': '1.3.6.1.4.1.1466.20037', 'value': None}> sent via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:PROTOCOL:new message id <1> generated
    DEBUG:ldap3:NETWORK:sending 1 ldap message for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:EXTENDED:ldap message sent via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>:
    >>LDAPMessage:
    >> messageID=1
    >> protocolOp=ProtocolOp:
    >>  extendedReq=ExtendedRequest:
    >>   requestName=b'1.3.6.1.4.1.1466.20037'
    DEBUG:ldap3:NETWORK:sent 31 bytes via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:NETWORK:received 14 bytes via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:NETWORK:received 1 ldap messages via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:EXTENDED:ldap message received via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>:
    <<LDAPMessage:
    << messageID=1
    << protocolOp=ProtocolOp:
    <<  extendedResp=ExtendedResponse:
    <<   resultCode='success'
    <<   matchedDN=b''
    <<   diagnosticMessage=b''
    DEBUG:ldap3:PROTOCOL:EXTENDED response <[{'referrals': None, 'dn': '', 'type': 'extendedResp', 'result': 0, 'description': 'success', 'responseName': None, 'responseValue': b'', 'message': ''}]> received via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:BASIC:done EXTENDED operation, result <True>
    DEBUG:ldap3:BASIC:tls started for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:NETWORK:socket wrapped with SSL using SSLContext for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: [None]:None - remote: [None]:None> - tls not started - listening - SyncStrategy>
    DEBUG:ldap3:BASIC:refreshing server info for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>
    DEBUG:ldap3:BASIC:done START TLS operation, result <True>


    # Performing Bind operation with Simple Bind method:

    DEBUG:ldap3:BASIC:start BIND operation via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>
    DEBUG:ldap3:PROTOCOL:performing simple BIND for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>
    DEBUG:ldap3:PROTOCOL:simple BIND request <{'name': 'cn=admin,o=test', 'authentication': {'sasl': None, 'simple': '<stripped 8 characters of sensitive data>'}, 'version': 3}> sent via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>
    DEBUG:ldap3:PROTOCOL:new message id <2> generated
    DEBUG:ldap3:NETWORK:sending 1 ldap message for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>
    DEBUG:ldap3:EXTENDED:ldap message sent via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>:
    >>LDAPMessage:
    >> messageID=2
    >> protocolOp=ProtocolOp:
    >>  bindRequest=BindRequest:
    >>   version=3
    >>   name=b'cn=admin,o=test'
    >>   authentication=AuthenticationChoice:
    >>    simple=b'<stripped 8 characters of sensitive data>'
    DEBUG:ldap3:NETWORK:sent 37 bytes via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>
    DEBUG:ldap3:NETWORK:received 14 bytes via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>
    DEBUG:ldap3:NETWORK:received 1 ldap messages via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>
    DEBUG:ldap3:EXTENDED:ldap message received via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>:
    <<LDAPMessage:
    << messageID=2
    << protocolOp=ProtocolOp:
    <<  bindResponse=BindResponse:
    <<   resultCode='success'
    <<   matchedDN=b''
    <<   diagnosticMessage=b''
    DEBUG:ldap3:PROTOCOL:BIND response <{'referrals': None, 'dn': '', 'type': 'bindResponse', 'result': 0, 'description': 'success', 'saslCreds': None, 'message': ''}> received via <ldap://openldap:389 - cleartext - user: cn=admin,o=test - unbound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>
    DEBUG:ldap3:BASIC:refreshing server info for <ldap://openldap:389 - cleartext - user: cn=admin,o=test - bound - open - <local: 192.168.137.1:49611 - remote: 192.168.137.104:389> - tls started - listening - SyncStrategy>
    DEBUG:ldap3:BASIC:done BIND operation, result <True>


These are the usage metrics of this session::

    Connection Usage:
     Time: [elapsed:        0:00:01.908938]
       Initial start time:  2015-06-02T09:37:49.451263
       Open socket time:    2015-06-02T09:37:49.451263
       Close socket time:
     Server:
       Servers from pool:   0
       Sockets open:        1
       Sockets closed:      0
       Sockets wrapped:     1
     Bytes:                 96
       Transmitted:         68
       Received:            28
     Messages:              4
       Transmitted:         2
       Received:            2
     Operations:            2
       Abandon:             0
       Bind:                1
       Add:                 0
       Compare:             0
       Delete:              0
       Extended:            1
       Modify:              0
       ModifyDn:            0
       Search:              0
       Unbind:              0
     Referrals:
       Received:            0
       Followed:            0
     Restartable tries:     0
       Failed restarts:     0
       Successful restarts: 0

As you can see there have been two operation, one for the bind and one for the startTLS (an extendend operation). One socket
has been open and has been wrapped in SSL. All the communication stream took 96 bytes in 4 LDAP messages.
