GithubHelp home page GithubHelp logo

noirello / bonsai Goto Github PK

View Code? Open in Web Editor NEW
115.0 115.0 32.0 1.75 MB

Simple Python 3 module for LDAP, using libldap2 and winldap C libraries.

License: MIT License

Python 56.64% C 40.45% Shell 0.86% PowerShell 1.05% Batchfile 0.53% Dockerfile 0.46%
c ldap python python3

bonsai's People

Contributors

chefkoch-de42 avatar lilydjwg avatar magnuswatn avatar mirraz avatar morian avatar noirello avatar rra avatar senfomat avatar yanonix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bonsai's Issues

Memmory leak

Good afternoon. I use a wonderful bonsai package with an asynchronous rpc aiomas server based on asyncio. I encountered a memory leak problem. When rpc works and LDAPClient () connect.search () is continually invoked, the system memory is noticeably consumed, without releasing it. This problem is only observed when I use the bonsai package. In the same cyclic call, for example other methods such as aiohttp.ClientSession, such a problem is not observed. The problem with the leak is well observed when calling tracemalloc. In the example, I showed what the problem is. If you use infinite call the pps method test, there is no such problem. If you call user_exist memory, it is absorbed before your eyes.

Tracemalloc before cycling call user_exist

[109] - Top 10 lines
[115] - #1: python3.6/_weakrefset.py:37: 5.2 KiB
[118] -     self.data = set()
[115] - #2: logging/__init__.py:1059: 5.1 KiB
[118] -     return open(self.baseFilename, self.mode, encoding=self.encoding)
[115] - #3: python3.6/_weakrefset.py:48: 4.4 KiB
[118] -     self._iterating = set()
[115] - #4: python3.6/_weakrefset.py:38: 4.2 KiB
[118] -     def _remove(item, selfref=ref(self)):
[115] - #5: python3.6/sre_parse.py:416: 3.8 KiB
[118] -     not nested and not items))
[115] - #6: asyncio/events.py:145: 2.9 KiB
[118] -     self._callback(*self._args)
[115] - #7: python3.6/sre_parse.py:112: 2.9 KiB
[118] -     self.pattern = pattern
[115] - #8: python3.6/sre_parse.py:623: 2.8 KiB
[118] -     subpattern[-1] = (MAX_REPEAT, (min, max, item))
[115] - #9: python3.6/_weakrefset.py:84: 2.7 KiB
[118] -     self.data.add(ref(item, self._remove))
[115] - #10: rpc-server.py:211: 2.1 KiB
[118] -     class RPCServer(Test, UserGroup):
[123] - 288 other: 115.1 KiB
[125] - Total allocated size: 151.3 KiB

Tracemalloc reading after 20 min of cyclic call

[109] - Top 10 lines
[115] - #1: bonsai/ldapentry.py:18: 69086.4 KiB
[118] -     super().__init__(str(dn), conn)
[115] - #2: python3.6/socket.py:489: 55269.7 KiB
[118] -     a = socket(family, type, proto, a.detach())
[115] - #3: python3.6/socket.py:490: 55269.2 KiB
[118] -     b = socket(family, type, proto, b.detach())
[115] - #4: python3.6/selectors.py:32: 27604.6 KiB
[118] -     if isinstance(fileobj, int):
[115] - #5: python3.6/selectors.py:457: 6151.9 KiB
[118] -     ready.append((key, events & key.events))
[115] - #6: python3.6/selectors.py:445: 3167.6 KiB
[118] -     fd_event_list = self._epoll.poll(timeout, max_ev)
[115] - #7: bonsai/ldapconnection.py:101: 1283.8 KiB
[118] -     return self._evaluate(super().open(), timeout)
[115] - #8: python3.6/_weakrefset.py:84: 563.7 KiB
[118] -     self.data.add(ref(item, self._remove))
[115] - #9: python3.6/linecache.py:137: 555.1 KiB
[118] -     lines = fp.readlines()
[115] - #10: asyncio/locks.py:221: 218.1 KiB
[118] -     self._waiters = collections.deque()
[123] - 399 other: 2285.5 KiB
[125] - Total allocated size: 221455.4 KiB

Here are the system readings before launch

VIRT   RES    SHR 
155М   29388  10192

Here are the system readings after half an hour of work

VIRT   RES    SHR 
978М   586M   10696

Нere is a simple server :

def display_top(snapshot, key_type='lineno', limit=10):
    snapshot = snapshot.filter_traces((
        tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
        tracemalloc.Filter(False, "<unknown>"),
    ))
    top_stats = snapshot.statistics(key_type)

    tracelog.info("------- Trace --------")
    tracelog.info("Top %s lines" % limit)
    for index, stat in enumerate(top_stats[:limit], 1):
        frame = stat.traceback[0]
        # replace "/path/to/module/file.py" with "module/file.py"
        filename = os.sep.join(frame.filename.split(os.sep)[-2:])
        tracelog.info("#%s: %s:%s: %.1f KiB"
                      % (index, filename, frame.lineno, stat.size / 1024))
        line = linecache.getline(frame.filename, frame.lineno).strip()
        if line:
            tracelog.info('    %s' % line)

    other = top_stats[limit:]
    if other:
        size = sum(stat.size for stat in other)
        tracelog.info("%s other: %.1f KiB" % (len(other), size / 1024))
    total = sum(stat.size for stat in top_stats)
    tracelog.info("Total allocated size: %.1f KiB" % (total / 1024))
    tracelog.info("-------- End ---------")



class RPCServer(Test, UserGroup):
    def __init__(self, lp, ldap):
        ldap_conf.update(ldap)

        ldap_url = "ldap://{}:{}".format(ldap_conf['host'],
                                         ldap_conf['port'])
        self.ldap_cli = LDAPClient(ldap_url)
        self.ldap_cli.set_credentials("SIMPLE",
                                 (ldap_conf['user'], ldap_conf['pswd']))
    router = aiomas.rpc.Service()

    @aiomas.expose
    async def test(self, args):
        async with ClientSession() as session:
            try:
                with timeout(9999):
                    async with session.get('http://python.org/') as r:
                        await r.text()
                        logging.info('test -->')
                        return dict(status=True, info='ok')

            except BaseException as e:
                return dict(status=False, info=str(e))
            finally:
                logging.info('test <--')

    @aiomas.expose
    async def trace(self):
        try:
            logging.info('trace -->')

            snapshot = tracemalloc.take_snapshot()
            display_top(snapshot)
            await asyncio.sleep(0.1)
        except BaseException as b:
            tracelog.error("Error: {}".format(b))
        finally:
            logging.info('trace <--')

            return dict(status=True, info='Done')

    @aiomas.expose
    async def user_exist(self, args):
        logging.info('--> user_exist')
        request = args.get('request')
        s_scope = args.get('s_scope', 2)
        base_dn = args.get('base_dn', 'dc=test,dc=wt')

        if not request:
            logging.info('<-- Required arguments: request')
            return dict(status=False, info='Required arguments: request')

        async with self.ldap_cli.connect(is_async=True,
                                         timeout=5) as conn:
            res = await conn.search(base_dn, s_scope, request)
            logging.info('<-- Res: {}'.format(res))
            if res:
                return dict(status=True, info=res)
            return dict(status=False, info='User does not exist')


if __name__ == '__main__':
    logging.info(' *** Begin *** ')
    loop = asyncio.get_event_loop()

    serv = RPCServer(lp=loop, ldap=conf['ldap'])
    try:
        server_rpc = aiomas.run(
            aiomas.rpc.start_server(
                ('127.0.0.1', 5000),
                serv,
            ),
        )

        aiomas.run(until=server_rpc.wait_closed())
    except BaseException as servexpt:
        logging.info('Core server brake: {}'.format(servexpt))

Client :


async def user_exist(user):
    rpc_con = await aiomas.rpc.open_connection(('127.0.0.1', 5000))
    res = await rpc_con.remote.user_exist(args=dict(base_dn='....',request='cn=...'))
    await rpc_con.close()
    return res

async def test():
    rpc_con = await aiomas.rpc.open_connection(('127.0.0.1', 5000))
    res = await rpc_con.remote.call(method='test', args=dict(verbosity=1))
    await rpc_con.close()
    return res

async def trace():
    rpc_con = await aiomas.rpc.open_connection(('127.0.0.1', 5000))
    res = await rpc_con.remote.trace()
    await rpc_con.close()
    return res

async def get_result(tasks):
    for i in asyncio.as_completed(tasks):
        res = await i
        print("Response: {}".format(res))


user = '52fce218-ba2d-46d9-bda2-0d166a6af80d'
users = ['','','','']

loop = asyncio.get_event_loop()

while True:
    coros = [user_exist(i) for i in users]
    loop.run_until_complete(get_result(coros))

paged_search, not working correctly

hey,

i have two different AD REALMS, one of them will only return exactly 1000 results for paged searches. where the other will return me way more results.

i already talked to the AD maintainers, and they cannot find an issue.

also ldapsearch will return the right result.

i also tried the "ldap3" python client. this client also returns the correct number of results.

any clue what is going on here?

here is some example code:

ldap3:

from ldap3 import Server, Connection
import ldap3

server = Server('dcexample.com', use_ssl=False, get_info=ldap3.ALL)
conn = Connection(server, user='[email protected]', password='password', auto_bind=True)

entries = conn.extend.standard.paged_search('dc=example,dc=com', '(objectClass=group)', attributes=['cn'], paged_size=1000)

results = list()

for entry in entries:
    results.append(entries)

print(len(results))

bonsai

import bonsai


client = bonsai.LDAPClient('ldap://dc.example.com')
client.set_credentials('SIMPLE', user='[email protected]', password='password')
conn = client.connect()
groups = conn.paged_search('DC=example,DC=com', bonsai.LDAPSearchScope.SUBTREE, "(objectClass=user)", page_size=1000)

results = []

for i in groups:
    results.append(i)
print(len(results))

i guess there is a configuration difference between the two AD realms, but since "ldapsearch" and the "ldap3" client return the right number of results, i feel like bonsai is doing something wrong here.

Tolerating DNs with whitespace

Hi,

I'm willing to believe the assertion that ldapdn.py only deals in valid DNs, and the unstated implication that DNs with whitespace in them may not be technically valid, however some records at my site have this affliction, and they seem to work fine with (almost) all other LDAP tooling I've used. I don't know if it's worth adding an option to permit this in bonsai, but incase anybody else finds themselves in this situation, one naive solution is to just remove whitespace from strdn at the top of LDAPDN's __init__ method in ldapdn.py, e.g.:

  
      def __init__(self, strdn: str) -> None:
+         strdn = strdn.replace(" ", "")
          if strdn != "" and not self._dnregex.match(strdn):
              raise InvalidDN(strdn)

Add binary for python 3.9

When installing with python 3.6, everything went fine. But when we upgraded to python 3.9, installing bonsai resulted in the message

error: microsoft visual c++ 14.0 or greater is required

Installing this on my workstation allowed the needed parts to get compiled and bonsai installed successfully. But our company does not allow installing development tools on production servers, so we will need a binary to deploy to our server environment.

Can you add the required wheel binaries to the pip package for python 3.9?

Question: what is the recommended way to do a concurrent search?

I found bonsai recently and I am trying to change the LDAP searching part in my project from ldap3 to bonsai since ldap3 is too slow especially establishing the first connection.

I need to search many data at the same time, so I wonder what is the recommended way to do this concurrent by using bonsai?

I worked out a coroutine way and a thread way:

# use coroutine

async def search(pool, mail):
    print(f'start search {mail}...')
    con = await pool.get()
    result = await con.search(
        BASE, bonsai.LDAPSearchScope.SUBTREE,
        f'(mail={mail})', attrlist=['uid']
    )
    await pool.put(con)
    print(f'end search {mail}!')
    return result


async def main():
    ml = ['[email protected]', '[email protected]', '[email protected]']
    client = bonsai.LDAPClient(LDAP_URL)
    pool = AIOConnectionPool(client, minconn=1, maxconn=5)
    await pool.open()
    rl = await asyncio.gather(*[search(pool, m) for m in ml])
    return rl

loop = asyncio.get_event_loop()
result = loop.run_until_complete(main())
# use thread

client = bonsai.LDAPClient(LDAP_URL)
pool = bonsai.pool.ThreadedConnectionPool(client, 1, 5)
pool.open()

def search(mail):
    print(f'start search {mail}...')
    con = pool.get()
    result = con.search(
        BASE, bonsai.LDAPSearchScope.SUBTREE,
        f'(mail={mail})', attrlist=['uid']
    )
    pool.put(con)
    print(f'end search {mail}!')
    return result


with multiprocessing.pool.ThreadPool(5) as p:
     result = p.map(search, ml)

but I am not sure which one is better and whether there is a better way than both these two.

It does not work LDAP PING

https://msdn.microsoft.com/en-us/library/cc223811.aspx
Ntver option is a byte string. When searching for the form:
conn.search(base='', scope=bonsai.LDAPSearchScope.BASE, filter='(&(DnsDomain=EKB2003.LOCAL)(NtVer=\00\00\00\01))', attrlist=['NETLOGON'])

I get the error:

TypeError: Wrong parameters (base<str|LDAPDN>, scope<int>, filter<str>, attrlist<List>, timeout<float>, attrsonly<bool>, sort_order<List>, page_size<int>, offset<int>, before_count<int>, after_count<int>, est_list_count<int>, attrvalue<object>).

How to fix it?

Connecting to trusting domain in different forest

How do I connect to a trusting domain in different forest? Is there any guidance around it?

I tried to connect to the trusting domain by providing the credentials of my base domain. My user account is created in "Domain_1" which is under "Master_Domain_1". I can authenticate to both these domains. I have Master_Domain_2 which is a separate forest that has "Domain_2" domain.

import bonsai
import configparser

bonsai.set_connect_async(False)

CACERT_FILE = "./Cert/cacerts.pem"

username = 'my_user'
password = 'SecretPassword#'

config = configparser.ConfigParser()
config.read('.//AD_Configs//Config.ini')
server_name = config['DOMAIN_1']['URL']

client = bonsai.LDAPClient(server_name)
client.set_ca_cert(CACERT_FILE)
client.set_auto_page_acquire(True)

client.set_credentials("SIMPLE", user="CN="+username+"OU=Users,DC=DOMAIN_1,DC=com", password=password)

base = config['DOMAIN_1']['Base']

# bonsai.set_connect_async(False)

conn = client.connect()
query = "(&(objectClass=group)(SamAccountName=Domain Admins))"

list_of_attributes = ['*']
final_result = []

with client.connect() as conn:
    result = conn.paged_search(base=base, scope=2, filter_exp=query, attrlist=['*'], 
                               page_size=1000, attrsonly=False)
    for r in result:
        final_result.append(r)
    print(len(final_result))
    msgid = result.acquire_next_page()
    while msgid is not None:
        result = conn._evaluate(msgid)
        for r in result:
            final_result.append(r)
        msgid = result.acquire_next_page()

print(len(final_result))

This code is successful when I change the DOMAIN_1 to Master_Domain_1 as well. When I change the server_name to reference the Domain_2 or Master_Domain_2 (also changed the base variable) where I have bi-directional trust I am not able to authenticate and I get the following error for conn = client.connect()-

bonsai.errors.AuthenticationError: Invalid credentials. 80090308: LdapErr: DSID-0C090447, comment: AcceptSecurityContext error, data 52e, v3839 (0x0031 [49])

The debug logs are -

DBG: ldapconnection_new [self:0x11790b760]
DBG: ldapconnection_init (self:0x11790b760)
DBG: ldapconnection_open (self:0x11790b760)
DBG: connecting (self:0x11790b760)
DBG: create_conn_info (mech:SIMPLE, sock:-1, creds:0x117905600)
DBG: ldapconnectiter_new [self:0x117916300]
DBG: create_init_thread_data (client:0x1178f6c40, sock:-1)
DBG: create_init_thread (ld:0x7fbba9ae51b0, info:0x7fbba9ae5b60, thread:0)
DBG: ldapconnection_result (self:0x11790b760, args:0x1162f0300, kwds:0x0)[msgid:-1]
DBG: LDAPConnection_Result (self:0x11790b760, msgid:-1, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x117916300, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:123145512566784, timeout:-1, misc:0x7fbba9ae51b0)
DBG: ldap_init_thread_func (params:0x7fbba9ae51b0)
DBG: _pthread_mutex_timedlock
DBG: set connecting async: 0
DBG: ldap_init_thread_func [retval:0]
ldap_create
ldap_url_parse_ext(ldaps://domain_2.com:636)
DBG: set_certificates (self:0x117916300)
DBG: binding [state:3]
ldap_sasl_bind
DBG: _ldap_bind (ld:0x7fbba9a77110, info:0x7fbba9ae5b60, ppolicy:0, result:0x0, msgid:0)
ldap_send_initial_request
ldap_new_connection 1 1 0
ldap_int_open_connection
ldap_connect_to_host: TCP domain_2.com:636
ldap_new_socket: 7
ldap_prepare_socket: 7
ldap_connect_to_host: Trying 10.210.21.193:636
ldap_pvt_connect: fd: 7 tm: -1 async: 0
attempting to connect: 
connect success
TLS trace: SSL_connect:before SSL initialization
TLS trace: SSL_connect:SSLv3/TLS write client hello
TLS trace: SSL_connect:SSLv3/TLS write client hello
TLS trace: SSL_connect:SSLv3/TLS read server hello
TLS certificate verification: depth: 3, err: 0, subject: 
TLS certificate verification: depth: 2, err: 0, subject: 
TLS certificate verification: depth: 1, err: 0, subject: 
TLS certificate verification: depth: 0, err: 0, subject: 
TLS trace: SSL_connect:SSLv3/TLS read server certificate
TLS trace: SSL_connect:SSLv3/TLS read server key exchange
TLS trace: SSL_connect:SSLv3/TLS read server certificate request
TLS trace: SSL_connect:SSLv3/TLS read server done
TLS trace: SSL_connect:SSLv3/TLS write client certificate
TLS trace: SSL_connect:SSLv3/TLS write client key exchange
TLS trace: SSL_connect:SSLv3/TLS write change cipher spec
TLS trace: SSL_connect:SSLv3/TLS write finished
TLS trace: SSL_connect:SSLv3/TLS write finished
TLS trace: SSL_connect:SSLv3/TLS read change cipher spec
TLS trace: SSL_connect:SSLv3/TLS read finished
ldap_open_defconn: successful
ldap_send_server_request
ldap_msgfree
DBG: LDAPConnectIter_Next (self:0x117916300, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
ldap_result ld 0x7fbba9a77110 msgid 1
wait4msg ld 0x7fbba9a77110 msgid 1 (infinite timeout)
wait4msg continue ld 0x7fbba9a77110 msgid 1 all 1
** ld 0x7fbba9a77110 Connections:
* host: domain_2.com  port: 636  (default)
  refcnt: 2  status: Connected
  last used: Tue Aug 10 14:54:56 2021
** ld 0x7fbba9a77110 Outstanding Requests:
 * msgid 1,  origid 1, status InProgress
   outstanding referrals 0, parent count 0
  ld 0x7fbba9a77110 request count 1 (abandoned 0)
** ld 0x7fbba9a77110 Response Queue:
   Empty
  ld 0x7fbba9a77110 response count 0
ldap_chkResponseList ld 0x7fbba9a77110 msgid 1 all 1
ldap_chkResponseList returns ld 0x7fbba9a77110 NULL
ldap_int_select
read1msg: ld 0x7fbba9a77110 msgid 1 all 1
read1msg: ld 0x7fbba9a77110 msgid 1 message type bind
read1msg: ld 0x7fbba9a77110 0 new referrals
read1msg:  mark request completed, ld 0x7fbba9a77110 msgid 1
request done: ld 0x7fbba9a77110 msgid 1
res_errno: 49, res_error: <80090308: LdapErr: DSID-0C090447, comment: AcceptSecurityContext error, data 52e, v3839>, res_matched: <>
ldap_free_request (origid 1, msgid 1)
ldap_parse_result
ldap_msgfree
ldap_err2string
DBG: ldapconnectiter_dealloc (self:0x117916300)
DBG: dealloc_conn_info (info:0x7fbba9ae5b60)
DBG: ldapconnection_dealloc (self:0x11790b520)
ldap_free_connection 1 1
ldap_send_unbind
TLS trace: SSL3 alert write:warning:close notify
ldap_free_connection: actually freed

What should I do differently ? I kept the user identity to my original domain_1 id and password as I use the same to authenticate with Powershell. (but RSAT module code be doing something in the background)

Invalid parsing of hostnames

Hi,
i have found some misttakes in the parsing regex inside LDAPURL.__str2url().

The expression to split URLs is (using re.VERBOSE):

r"""
    ^
    (ldap[s|i]?)                    # (1: scheme)
    ://
    (                               # (2:
        ([^:/?]*)?                  #   (3: host)
        (                           #   (4
            [:]                     #       ':'
            ([1-9][0-9]{0,4})       #       (5: port)
        )?                          #   )?
        
        |                           #   --OR--
        
        [\[]?                       #   '['?
        ([^/?\]]*)                  #   (6: host)
        (                           #   (7:
            [\]]                    #       ']'
            [:]                     #       ':'
            ([1-9][0-9]{0,4})       #       (8: port)
        )?                          #   )
    )                               # )
    [/]?
    ([^\]/:?]*)?                    # (9: bind DN)
    [\?]?
    ([^\]:?]*)?                     # (10: attributes)
    [\?]?
    ([^\]:?]*)?                     # (11: scope)
    [\?]?
    ([^\]:?]*)?                     # (12: filter)
    [\?]?
    ([^\]:?]*)?                     # (13: extensions)
    $
"""

So you can have a scheme of "ldap", "ldaps", "ldap|" or "ldapi".
I think you mean (ldap[si]?) instead?

The server part (group 2) matches i.e. on

    (empty string)
    host
    [host         # Error
    host:99
    host]:99      # Error
    [host]:99
    :99
    ]:99          # Error
    []:99         # Error?

But never ever you can have a "[host]" without specifying port number (because the closing bracket is part of group 7).

Additionally, the separator between the server part (group 2) and the possibly following base-dn (group 9) can be omitted. But that can't work.

My suggestion is to use urlpase or urlsplit from urllib.parse - see urllib.parse — Split URLs into Components

To do this you need to extend the following class variables of urllib.parse with the ldap schemes {'ldap', 'ldaps', 'ldapi'}:

    urllib.parse.uses_netloc
    urllib.parse.uses_query
    urllib.parse.uses_params    # maybe

Btw: is_valid_hostname:
To parse or check any raw ip addresses use ipaddress.ip_address. It returns an address object (IPv4Address or IPv6Address) with a version attribute showing a value of 4 or 6 respectively.

Is there a way to make the connection auto reconnect while network restored?

In an unstable network environment, once the network down, the LDAP search will never success even after the network connection is back. Is there an auto reconnect flag or some other ways to make searches work after the network is restored?

import bonsai

client = bonsai.LDAPClient(ldap_url)
conn = client.connect(timeout=3)
conn.search(base, scope, filter, timeout=3)  # here it works
# here the network was gone
conn.search(base, scope, filter, timeout=3) # timeout error, of course
# here the network restored
conn.search(base, scope, filter, timeout=3) # still timeout error! I hope it can work here.
print(conn.closed)  # it is False here, so I cannot manually reconnect it with a check of close status

Cannot use tls

Not sure if I'm doing something wrong or this is a bug. I can't get tls turned on with LDAPClient.

>>> from bonsai import LDAPClient
>>> LDAPClient('ldaps://localhost').tls
False
>>> LDAPClient('ldaps://localhost', tls=True).tls
False

This is with bonsai==1.1.0

paged_search is limited to 1000 results

Even with auto next page, it seems to be limited at 1000, acquire_next_page even returns None.

Python on Windows 10 running against an AD search is expected to return 1020 results 😓

"Can't contact LDAP server" on 1.0.0 version

I've update bonsai from 0.9.1 to 1.0.0 and it has started to get connection error "bonsai.errors.ConnectionError: Can't contact LDAP server. (0xFFFF [-1])" every time, no matter if I set credentials / certificate options or not. Debug output:

client = bonsai.LDAPClient("ldaps://my.ldap:636")
client.set_credentials('simple', user='ou=condn', password='password')
conn = client.connect()
DBG: ldapconnection_new [self:0x7fd9c87fdd08]
DBG: ldapconnection_init (self:0x7fd9c87fdd08)
DBG: ldapconnection_open (self:0x7fd9c87fdd08)
DBG: connecting (self:0x7fd9c87fdd08)
DBG: create_conn_info (mech:SIMPLE, sock:-1, creds:0x7fd9af8f8240)
DBG: ldapconnectiter_new [self:0x7fd9af8f8f18]
DBG: create_init_thread_data (client:0x7fd9ae68d940, sock:-1)
DBG: create_init_thread (ld:0x29145e0, info:0x2913460, thread:0)
DBG: ldapconnection_result (self:0x7fd9c87fdd08, args:0x7fd9ae6940c8, kwds:(nil))[msgid:-1]
DBG: LDAPConnection_Result (self:0x7fd9c87fdd08, msgid:-1, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7fd9af8f8f18, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140573321987840, timeout:-1, misc:0x29145e0)
DBG: _pthread_mutex_timedlock
DBG: LDAPConnectIter_Next (self:0x7fd9af8f8f18, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140573321987840, timeout:-1, misc:0x29145e0)
DBG: _pthread_mutex_timedlock
ldap_create
ldap_url_parse_ext(ldaps://my.ldap:636)
ldap_sasl_bind
ldap_send_initial_request
ldap_new_connection 1 1 0
ldap_int_open_connection
ldap_connect_to_host: TCP my.ldap:636
ldap_new_socket: 19
ldap_prepare_socket: 19
ldap_connect_to_host: Trying 10.0.0.1:636
ldap_pvt_connect: fd: 19 tm: 0 async: -1
ldap_ndelay_on: 19
attempting to connect: 
connect errno: 115
ldap_open_defconn: successful
ldap_send_server_request
ldap_msgfree
ldap_result ld 0x7fd9b40076d0 msgid 1
wait4msg ld 0x7fd9b40076d0 msgid 1 (infinite timeout)
wait4msg continue ld 0x7fd9b40076d0 msgid 1 all 1
** ld 0x7fd9b40076d0 Connections:
* host: my.ldap  port: 636  (default)
  refcnt: 2  status: Connected
  last used: Fri Jan 25 16:11:58 2019
** ld 0x7fd9b40076d0 Outstanding Requests:
 * msgid 1,  origid 1, status Writing
   outstanding referrals 0, parent count 0
  ld 0x7fd9b40076d0 request count 1 (abandoned 0)
** ld 0x7fd9b40076d0 Response Queue:
   Empty
  ld 0x7fd9b40076d0 response count 0
ldap_chkResponseList ld 0x7fd9b40076d0 msgid 1 all 1
ldap_chkResponseList returns ld 0x7fd9b40076d0 NULL
ldap_int_select
wait4msg continue ld 0x7fd9b40076d0 msgid 1 all 1
** ld 0x7fd9b40076d0 Connections:
* host: my.ldap  port: 636  (default)
  refcnt: 2  status: Connected
  last used: Fri Jan 25 16:11:58 2019
** ld 0x7fd9b40076d0 Outstanding Requests:
 * msgid 1,  origid 1, status InProgress
   outstanding referrals 0, parent count 0
  ld 0x7fd9b40076d0 request count 1 (abandoned 0)
** ld 0x7fd9b40076d0 Response Queue:
   Empty
  ld 0x7fd9b40076d0 response count 0
ldap_chkResponseList ld 0x7fd9b40076d0 msgid 1 all 1
ldap_chkResponseList returns ld 0x7fd9b40076d0 NULL
ldap_int_select
DBG: ldap_init_thread_func (params:0x29145e0)
DBG: set_cert_policy (ld:0x7fd9b40076d0, cert_policy:4)
DBG: ldap_init_thread_func [retval:0]
DBG: LDAPConnectIter_Next (self:0x7fd9af8f8f18, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140573321987840, timeout:-1, misc:0x29145e0)
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x7fd9af8f8f18)
DBG: binding [state:3]
DBG: _ldap_bind (ld:0x7fd9b40076d0, info:0x2913460, ppolicy:0, result:(nil), msgid:0)
DBG: LDAPConnectIter_Next (self:0x7fd9af8f8f18, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnectiter_dealloc (self:0x7fd9af8f8f18)
DBG: dealloc_conn_info (info:0x2913460)
read1msg: ld 0x7fd9b40076d0 msgid 1 all 1
ldap_msgfree
ldap_err2string
Traceback (most recent call last):
  File "/home/pkirillov/.virtualenvs/amp/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2961, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-29-a00ee1f64c39>", line 1, in <module>
    conn = client.connect()
  File "/home/pkirillov/.virtualenvs/amp/lib/python3.6/site-packages/bonsai/ldapclient.py", line 577, in connect
    return LDAPConnection(self).open(timeout)
  File "/home/pkirillov/.virtualenvs/amp/lib/python3.6/site-packages/bonsai/ldapconnection.py", line 292, in open
    return super().open(timeout)
  File "/home/pkirillov/.virtualenvs/amp/lib/python3.6/site-packages/bonsai/ldapconnection.py", line 53, in open
    return self._evaluate(super().open(), timeout)
  File "/home/pkirillov/.virtualenvs/amp/lib/python3.6/site-packages/bonsai/ldapconnection.py", line 241, in _evaluate
    return self.get_result(msg_id, timeout)
bonsai.errors.ConnectionError: Can't contact LDAP server. (0xFFFF [-1])

And debug output from 0.9.1 version:

client = bonsai.LDAPClient("ldaps://my.ldap:636")
client.set_credentials('simple', ('ou=conndn', 'password'))
conn = client.connect()
DBG: ldapconnection_new [self:0x7f9e1a63bd08]
DBG: ldapconnection_init (self:0x7f9e1a63bd08)
DBG: ldapconnection_open (self:0x7f9e1a63bd08)
DBG: connecting (self:0x7f9e1a63bd08)
DBG: create_conn_info (mech:SIMPLE, sock:-1, creds:0x7f9e084eed48)
DBG: ldapconnectiter_new [self:0x7f9e0973c9c0]
DBG: create_init_thread_data (client:0x7f9e09781080, sock:-1)
DBG: create_init_thread (ld:0x2323cf0, info:0x2014a00, thread:0)
DBG: ldapconnection_result (self:0x7f9e1a63bd08, args:0x7f9e084f08c8, kwds:(nil))[msgid:-1]
DBG: LDAPConnection_Result (self:0x7f9e1a63bd08, msgid:-1, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7f9e0973c9c0, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140316997838592, timeout:-1, misc:0x2323cf0)
DBG: ldap_init_thread_func (params:0x2323cf0)
DBG: set_cert_policy (ld:0x7f9e0400fcd0, cert_policy:4)
DBG: ldap_init_thread_func [retval:0]
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x7f9e0973c9c0)
DBG: binding [state:3]
DBG: _ldap_bind (ld:0x7f9e0400fcd0, info:0x2014a00, ppolicy:0, result:(nil), msgid:0)
ldap_create
ldap_url_parse_ext(ldaps://my.ldap:636)
ldap_sasl_bind
ldap_send_initial_request
ldap_new_connection 1 1 0
ldap_int_open_connection
ldap_connect_to_host: TCP my.ldap:636
ldap_new_socket: 10
ldap_prepare_socket: 10
ldap_connect_to_host: Trying 10.0.0.1:636
ldap_pvt_connect: fd: 10 tm: -1 async: 0
attempting to connect: 
connect success
ldap_open_defconn: successful
ldap_send_server_request
ldap_msgfree
ldap_result ld 0x7f9e0400fcd0 msgid 1
wait4msg ld 0x7f9e0400fcd0 msgid 1 (infinite timeout)
wait4msg continue ld 0x7f9e0400fcd0 msgid 1 all 1
** ld 0x7f9e0400fcd0 Connections:
* host: my.ldap  port: 636  (default)
  refcnt: 2  status: Connected
  last used: Fri Jan 25 16:30:09 2019
** ld 0x7f9e0400fcd0 Outstanding Requests:
 * msgid 1,  origid 1, status InProgress
   outstanding referrals 0, parent count 0
  ld 0x7f9e0400fcd0 request count 1 (abandoned 0)
** ld 0x7f9e0400fcd0 Response Queue:
   Empty
  ld 0x7f9e0400fcd0 response count 0
ldap_chkResponseList ld 0x7f9e0400fcd0 msgid 1 all 1
ldap_chkResponseList returns ld 0x7f9e0400fcd0 NULL
ldap_int_select
read1msg: ld 0x7f9e0400fcd0 msgid 1 all 1
read1msg: ld 0x7f9e0400fcd0 msgid 1 message type bind
read1msg: ld 0x7f9e0400fcd0 0 new referrals
read1msg:  mark request completed, ld 0x7f9e0400fcd0 msgid 1
request done: ld 0x7f9e0400fcd0 msgid 1
res_errno: 0, res_error: <>, res_matched: <>
ldap_free_request (origid 1, msgid 1)
ldap_parse_result
ldap_msgfree
DBG: LDAPConnectIter_Next (self:0x7f9e0973c9c0, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnectiter_dealloc (self:0x7f9e0973c9c0)
DBG: dealloc_conn_info (info:0x2014a00)
DBG: ldapconnection_dealloc (self:0x7f9e18d570a8)

ldap lib versions:
Ubuntu 18.10: libldap-2.4-2/cosmic-updates,now 2.4.46+dfsg-5ubuntu1.1 amd64
Centos 7.5: openldap-2.4.44-15.el7_5.x86_64

Problem with filedescriptorleak on ldapi-Connections when passing wrong credentials

The Setup

We are using bonsai to authenticate OpenVPN-users via a Python-Flask-Webservice.

We are using:

  • bonsai, v1.2.0 installed via pip
  • libldap-2.4-2, v2.4.49+dfsg-2ubuntu1.5
  • libldap2-dev, v2.4.49+dfsg-2ubuntu1.5
  • OS: Ubuntu 20.04.1 LTS

For the authentication we have the following function:

def LDAPAuthenticate(username, password):
    client = LDAPClient("ldapi://%2Frun%2Fslapd%2Fldapi")
    client.set_credentials("SIMPLE", user=f"uid={username},cn=people,dc=domain.org", password=password)

    session = False
    try:
        client.connect():
    except AuthenticationError as err:
        log_error(f"LDAP-Authentication for '{username}' failed: {err}")
    except Exception as e:
        log_error(f"LDAP-Service failed: {e}")
    else:
        log_debug(f"LDAP-Authentication for '{username}' succeeded.")
        session = True
        
    return session

The Problem

When a users enters a wrong password within this authentication, every call to LDAPAuthenticate() leaks around 14 filedescriptors on the ldapi-Socketconnection, which gets not closed after the connect()-call. These connections are only closed, when the python3-webservice process exits or the slapd gets restarted.

When this wrong authentication is done a few hundred times (as some of our users have misconfigured clients), the ldap-daemon stops responding at all, as the number of maximum open filedescriptors is exhausted.

We have installed a workaround to restart the gunicorn-Webservice-Processes after 100 Requests. That we can not reach the fd-limit on the slapd.

Debuglogs

The connection-debug of a failed connection (using bonsai.set_debug(True)):

DBG: ldapconnection_new [self:0x7ff8b0ad0340]
DBG: ldapconnection_init (self:0x7ff8b0ad0340)
DBG: ldapconnection_open (self:0x7ff8b0ad0340)
DBG: connecting (self:0x7ff8b0ad0340)
DBG: create_conn_info (mech:SIMPLE, sock:-1, creds:0x7ff8b1dbcc40)
DBG: ldapconnectiter_new [self:0x7ff8b0ad6440]
DBG: create_init_thread_data (client:0x7ff8b1da1a00, sock:-1)
DBG: create_init_thread (ld:0x151a2e0, info:0x151a290, thread:0)
DBG: ldapconnection_result (self:0x7ff8b0ad0340, args:0x7ff8b0ad9c00, kwds:(nil))[msgid:-1]
DBG: LDAPConnection_Result (self:0x7ff8b0ad0340, msgid:-1, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7ff8b0ad6440, timeout:-1) [tls:0, state:0]
DBG: ldap_init_thread_func (params:0x151a2e0)DBG: _ldap_finish_init_thread (async:0, thread:140706090530560, timeout:-1, misc:0x151a2e0)
DBG: _pthread_mutex_timedlock
DBG: set connecting async: 1
DBG: ldap_init_thread_func [retval:0]
DBG: LDAPConnectIter_Next (self:0x7ff8b0ad6440, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140706090530560, timeout:-1, misc:0x151a2e0)
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x7ff8b0ad6440)
DBG: binding [state:3]
DBG: _ldap_bind (ld:0x7ff8ac000b60, info:0x151a290, ppolicy:0, result:(nil), msgid:0)
DBG: LDAPConnectIter_Next (self:0x7ff8b0ad6440, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnectiter_dealloc (self:0x7ff8b0ad6440)
DBG: dealloc_conn_info (info:0x151a290)
LDAP-Authentication for 'senfomat' failed: Invalid credentials. (0x0031 [49])
DBG: ldapconnection_dealloc (self:0x7ff8b0ad0340)

The connection with a working auth looks like this:

DBG: ldapconnection_new [self:0x7fb50112b340]
DBG: ldapconnection_init (self:0x7fb50112b340)
DBG: ldapconnection_open (self:0x7fb50112b340)
DBG: connecting (self:0x7fb50112b340)
DBG: create_conn_info (mech:SIMPLE, sock:-1, creds:0x7fb502417c40)
DBG: ldapconnectiter_new [self:0x7fb5011314e0]
DBG: create_init_thread_data (client:0x7fb502309b50, sock:-1)
DBG: create_init_thread (ld:0x1e34740, info:0x1e51290, thread:0)
DBG: ldapconnection_result (self:0x7fb50112b340, args:0x7fb501134b40, kwds:(nil))[msgid:-1]
DBG: LDAPConnection_Result (self:0x7fb50112b340, msgid:-1, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7fb5011314e0, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140415381595904, timeout:-1, misc:0x1e34740)
DBG: _pthread_mutex_timedlock
DBG: ldap_init_thread_func (params:0x1e34740)
DBG: set connecting async: 1
DBG: ldap_init_thread_func [retval:0]
DBG: LDAPConnectIter_Next (self:0x7fb5011314e0, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140415381595904, timeout:-1, misc:0x1e34740)
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x7fb5011314e0)
DBG: binding [state:3]
DBG: _ldap_bind (ld:0x7fb4fc002fc0, info:0x1e51290, ppolicy:0, result:(nil), msgid:0)
DBG: LDAPConnectIter_Next (self:0x7fb5011314e0, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnectiter_dealloc (self:0x7fb5011314e0)
DBG: dealloc_conn_info (info:0x1e51290)
DBG: ldapconnection_dealloc (self:0x7fb50112b340)
LDAP-Authentication for 'senfotest' succeeded.

We are not sure, why the bonsai-Module is doing the connection in async mode. We do not want this, we left it on the default of False. But as the debuglog shows in the middle, the module is forcing it to async.

Asynchronous operations without asyncio

I've been trying to do an asynchronous LDAP-search with PyLDAP. But for the app I would like to make, there is already an eventloop (which is not based python3.4's asyncio). I could not see a way how to do asynchronous operations without having an asyncio-eventloop.

While I think it is a very nice idea to support asyncio's yield from pattern from a ldap-module, I would consider it a "bug" if it is not possible to do asynchronous operations without it.

[edit] Put quotes around "bug". [/edit]

LDAPClient.connect not a coroutine

Any chance of making it one? Its takes a non-trivial amount of time and if the loop could be doing things while waiting on network things that would be great.

Good work by the way.

Cannot change "userAccountControl" attribute

Hi,
thank you for developing bonsai, the API is much nicer than python-ldap. I have a problem that when I create a new user with bonsai via AD, then I cannot set the userAccountControl attribute to enable the user:

    entry = LDAPEntry(user.distinguished_name)
    entry["cn"] = user.fullname
    entry["givenName"] = user.firstname
    entry["sn"] = user.lastname
    entry["userPrincipalName"] = user.account_name
    entry["displayName"] = user.fullname
    entry["objectclass"] = "user",
    entry["sAMAccountName"] = user.account_name
    entry["userPassword"] = user.password
    entry["mail"] = user.email

    with self._build_client().connect() as conn:
        res = conn.add(entry)
        print(res)

        # http://www.selfadsi.de/ads-attributes/user-userAccountControl.htm
        entry.change_attribute("userAccountControl", LDAPModOp.REPLACE, 66048 )
        entry.modify()

The account is created but the modify fails:

bonsai.errors.UnwillingToPerform: Unwilling To Perform. (0x0035 [53])

Do you have an idea what is wrong? I checked already the password, it adheres the AD policies and I also tried setting 512 instead of 66048. I also tried setting it in the initial create, but the error is then the same.

Getting all the attributes from AD

I am trying to get all attributes related to users so when I use attrlist=['*'] in the paged search I get about 125 attributes. When I tried the Get-ADUser in Powershell to to enumerate all the attributes there are about 205. I am trying to get the remaining attributes as well is there a way to do that? The missing attributes include -

AccountNotDelegated, AllowReversiblePasswordEncryption, BadLogonCount, CannotChangePassword, DoesNotRequirePreAuth etc.

example code -

with client.connect() as conn:
    result = conn.paged_search(base=base, scope=2, filter_exp=query, attrlist=['*'], 
                               page_size=1000, attrsonly=False)

Powershell code -

Get-ADUser -Server $Server -Filter $Filter -ResultPageSize 1000 -Properties *

Async with connection pools

I think connection pools do not work well with async. I may be doing something wrong of course. Here goes nothing:

import asyncio
import bonsai

client = bonsai.LDAPClient("ldap://opendj:1389")
client.set_credentials(mechanism="SIMPLE", user="cn=Directory Manager", password="password")
pool = bonsai.pool.ConnectionPool(client, minconn=1, maxconn=2, is_async=True)
pool.open()

async def test():
    conn = await pool.get()
    pool.put(conn)
    conn = await pool.get()


loop = asyncio.get_event_loop()
result = loop.run_until_complete(test())

This results in

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-11-07b69a060a6c> in <module>
     14
     15 loop = asyncio.get_event_loop()
---> 16 result = loop.run_until_complete(test())

/usr/local/lib/python3.8/asyncio/base_events.py in run_until_complete(self, future)
    614             raise RuntimeError('Event loop stopped before Future completed.')
    615
--> 616         return future.result()
    617
    618     def stop(self):

<ipython-input-11-07b69a060a6c> in test()
     10     conn = await pool.get()
     11     pool.put(conn)
---> 12     conn = await pool.get()
     13
     14

RuntimeError: cannot reuse already awaited coroutine

What also makes me think that the async implementation was not designed to work with connection pools is the fact that pool.spawn does not work at all because it is wrapped in contextlib.contextmanager and obviously lacks __aenter__ and __aexit__ methods so it fails when used with async with.

Any advice on how to bite this? I am designing an application for quite high throughput.

EDIT:

Okay, I think I have hacked my way around it like this

import asyncio
from contextlib import asynccontextmanager

import bonsai


class AsyncConnectionPool(bonsai.pool.ConnectionPool):
    def __init__(self, *args, **kwargs):
        super().__init__(is_async=True, *args, **kwargs)

    @asynccontextmanager
    async def spawn(self, *args, **kwargs):
        try:
            if self._closed:
                self.open()
            conn = self.get(*args, **kwargs)
            if conn.closed:
                await conn.open()
            yield conn
        finally:
            self.put(conn)


client = bonsai.LDAPClient("ldap://opendj:1389")
client.set_credentials(mechanism="SIMPLE", user="cn=dm", password="cangetinds")
pool = AsyncConnectionPool(client, minconn=1, maxconn=2)


async def test():
    async with pool.spawn() as conn1:
        print(await conn1.whoami())
        async with pool.spawn() as conn2:
            print(await conn2.whoami())
    async with pool.spawn() as conn1:
        print(await conn1.whoami())
            

loop = asyncio.get_event_loop()
result = loop.run_until_complete(test())

This works fine, but now I get this ugly warning

/home/remy/.local/lib/python3.8/site-packages/bonsai/asyncio/aioconnection.py:70: RuntimeWarning: coroutine 'AIOLDAPConnection._poll' was never awaited
  self.__open_coro = super().open(timeout)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

not sure what to do with this and what the side effects are.

When LDAP server unavailable whole application freezes

Hello,

I found nasty bug that freezes my application :(
Steps to reproduce:

import asyncio
import time

import bonsai

async def connect():
    client = bonsai.LDAPClient(f"ldap://8.8.8.8")
    async with client.connect(is_async=True, timeout=3):
        return True

async def printer():
    while True:
        print(f"{time.time()} hi")
        await asyncio.sleep(1)

async def main():
    asyncio.create_task(printer())
    await asyncio.sleep(3)
    await connect()

asyncio.run(main())
1596881355.416916 hi
1596881356.420735 hi
1596881357.4230669 hi
Traceback (most recent call last):
  File ".../script_bonsai.py", line 25, in <module>
    asyncio.run(main())
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 583, in run_until_complete
    return future.result()
  File ".../script_bonsai.py", line 22, in main
    await connect()
  File ".../script_bonsai.py", line 9, in connect
    async with client.connect(is_async=True, timeout=3):
  File ".../lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 30, in __aenter__
    return await self.__open_coro
  File ".../lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 63, in _poll
    raise exc
  File ".../lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 59, in _poll
    return await asyncio.wait_for(fut, timeout)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/tasks.py", line 442, in wait_for
    return fut.result()
  File ".../lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 45, in _ready
    res = super().get_result(msg_id)
bonsai.errors.ConnectionError: Can't contact LDAP server. (0xFFFF [-1])
1596881434.8999639 hi

Process finished with exit code 1

I think super().get_result(msg_id) should be executed in run_in_executor with wait_for

bonsai.errors.LDAPError: Operations error

Hi, I am running into the following error with the code.

I am working with a virtual active directory connection,(Ldap3 over SSL)

Code:

compiled_search_filter = '(&(objectCategory=person)(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=CN=ADgroup,OU=test,OU=sys,DC=a,DC=b,DC=com))'
async with client.connect(is_async=True) as conn:
        res = []
        result = await conn.paged_search(base="", scope=2, filter_exp=compiled_search_filter,
                                         attrlist=['employeeID', 'sAMAccountName','objectClass','DistinguishedName'],
                                         timeout=10,attrsonly=True,page_size=1000)
        async for r in result:
            res.append(r)

Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 3326, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "", line 1, in
runfile('/Users/bawer/Documents/Automation Projects/Test/Bonsai_AD_Domains.py', wdir='/Users/bawer/Documents/Automation Projects/Test')
File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_bundle/pydev_umd.py", line 197, in runfile
pydev_imports.execfile(filename, global_vars, local_vars) # execute the script
File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/Users/bawer/Documents/Automation Projects/Test/Bonsai_AD_Domains.py", line 87, in
res = loop.run_until_complete(get_members(group_name))
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 579, in run_until_complete
return future.result()
File "/Users/bawer/Documents/Automation Projects/Test/Bonsai_AD_Domains.py", line 70, in get_members
attrlist=['employeeID', 'sAMAccountName','objectClass','DistinguishedName'],timeout=10,attrsonly=True,page_size=1000)
File "/usr/local/lib/python3.7/site-packages/bonsai-1.2.0-py3.7-macosx-10.14-x86_64.egg/bonsai/asyncio/aioconnection.py", line 64, in _poll
debug.txt

raise exc

File "/usr/local/lib/python3.7/site-packages/bonsai-1.2.0-py3.7-macosx-10.14-x86_64.egg/bonsai/asyncio/aioconnection.py", line 59, in _poll
return await asyncio.wait_for(fut, timeout)
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/tasks.py", line 442, in wait_for
return fut.result()
File "/usr/local/lib/python3.7/site-packages/bonsai-1.2.0-py3.7-macosx-10.14-x86_64.egg/bonsai/asyncio/aioconnection.py", line 45, in _ready
res = super().get_result(msg_id)
bonsai.errors.LDAPError: Operations error. (0x0001 [1])

The query seems to work with JXplorer. It seems the connection might be timing out.

Debug logs attached

With is_async=False I get the following error -

File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 579, in run_until_complete
return future.result()
File "/Users/bawer/Documents/Automation Projects/Test/Bonsai_AD_Domains.py", line 23, in find_dn
async with client.connect(is_async=False) as conn:
AttributeError: aexit

TLS/ldaps ConnectionError: Can't contact LDAP server. (unknown error code) with Asyncio

Hello! I really appreciate the work you have put into this library!

I am getting an issue where if I try to enable ldaps/TLS in my code, it fails to connect to the server.

When I was using the ldap3 library it would be able to connect to the server with ldaps so I am unsure why I am having this issue.

For example, when I just have TLS enabled:

#!/usr/bin/env python3

import asyncio
import bonsai

async def do():
    client = bonsai.LDAPClient(f"ldap://{remote}", True)
    client.set_credentials("SIMPLE", user=username, password=passphrase)
    async with client.connect(is_async=True) as conn:
        who = await conn.whoami()
        print(who)

loop = asyncio.get_event_loop()
loop.run_until_complete(do())

It returns this Traceback:

Traceback (most recent call last):
  File "./testing.py", line 15, in <module>
    loop.run_until_complete(do())
  File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "./testing.py", line 10, in do
    async with client.connect(is_async=True) as conn:
  File "/home/testing/.local/lib/python3.8/site-packages/bonsai/asyncio/aioconnection.py", line 25, in __aenter__
    return await self.__open_coro
  File "/home/testing/.local/lib/python3.8/site-packages/bonsai/asyncio/aioconnection.py", line 59, in _poll
    raise exc
  File "/home/testing/.local/lib/python3.8/site-packages/bonsai/asyncio/aioconnection.py", line 54, in _poll
    return await asyncio.wait_for(fut, timeout)
  File "/usr/lib/python3.8/asyncio/tasks.py", line 455, in wait_for
    return await fut
  File "/home/testing/.local/lib/python3.8/site-packages/bonsai/asyncio/aioconnection.py", line 40, in _ready
    res = super().get_result(msg_id)
bonsai.errors.ConnectionError: Connect error. (unknown error code) (0xFFF5 [-11])

It shows the same results when I use ldaps.

Do you have any insight into this issue?

Unexpected behavior on macOS 10.12.3 (Python 3.6)

Hi!

I got some very strange behavior with bonsai on macOS, first connection never works properly.
Here my code example:

import asyncio
import bonsai


@asyncio.coroutine
def do():
    client = bonsai.LDAPClient('ldaps://secret')
    client.set_credentials('SIMPLE', ('secret', 'secret'))
    client.set_cert_policy('allow')

    with (yield from client.connect(is_async=True, timeout=10.0)) as conn:
        res = yield from conn.search('ou=nerdherd,dc=bonsai,dc=test', 2)
        print(res)
        who = yield from conn.whoami()
        print(who)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(do())
    loop.run_until_complete(do())

And console output with enabled PYTHONASYNCIODEBUG env var:

➔ PYTHONASYNCIODEBUG=1 python test_bonsai.py
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.108 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.107 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.111 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.103 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.106 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.110 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.112 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.113 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.105 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.110 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.103 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.103 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.107 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.105 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.103 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.101 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.111 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.107 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.106 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.102 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.106 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.110 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.102 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.111 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.105 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.105 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.102 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.107 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.293 seconds
[]
dn:cn=secret
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:262> took 0.263 seconds
[]
dn:cn=secret

Sometimes first connection doesn't work at all...

Same code on Linux works well:

root@e2c92ff9d4f6:/tmp# PYTHONASYNCIODEBUG=1 python test_bonsai.py
Executing <Handle cancelled AIOLDAPConnection._ready(9, <Future pendi...nection.py:31>) created at /usr/local/lib/python3.6/asyncio/selector_events.py:298> took 0.437 seconds
[]
dn:cn=secret
Executing <Handle cancelled AIOLDAPConnection._ready(9, <Future pendi...nection.py:31>) created at /usr/local/lib/python3.6/asyncio/selector_events.py:262> took 0.368 seconds
[]
dn:cn=secret
root@e2c92ff9d4f6:/tmp#

Any ideas?

Unable to import os macOS 10.12.3 (Python 3.6)

Hi,

Just tried to use library and got exception on import:

In [1]: import bonsai
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-1-52837d26041c> in <module>()
----> 1 import bonsai

/Users/ng/.virtualenvs/env/lib/python3.6/site-packages/bonsai/__init__.py in <module>()
      1 from .ldapdn import LDAPDN
      2 from .ldapurl import LDAPURL
----> 3 from .ldapconnection import LDAPConnection
      4 from .ldapconnection import LDAPSearchScope
      5 from .ldapentry import LDAPEntry

/Users/ng/.virtualenvs/env/lib/python3.6/site-packages/bonsai/ldapconnection.py in <module>()
      2 from typing import Union, Any, List, Iterator, Tuple
      3
----> 4 from ._bonsai import ldapconnection
      5 from .ldapdn import LDAPDN
      6 from .ldapentry import LDAPEntry

ImportError: dlopen(/Users/ng/.virtualenvs/env/lib/python3.6/site-packages/bonsai/_bonsai.cpython-36m-darwin.so, 2): Symbol not found: _ldap_create_passwordpolicy_control
  Referenced from: /Users/ng/.virtualenvs/env/lib/python3.6/site-packages/bonsai/_bonsai.cpython-36m-darwin.so
  Expected in: flat namespace
 in /Users/ng/.virtualenvs/env/lib/python3.6/site-packages/bonsai/_bonsai.cpython-36m-darwin.so

In [2]:

I use latest version of bonsai from PyPI and Python 3.6.
Any ideas?

Setting bonsai.set_connect_async(True) causes LDAPS connection to hang until the timeout is reached

Testing on Fedora30 with module built against openldap-devel-2.4.47-1.fc30.x86_64

Code to reproduce:

import asyncio
import bonsai

bonsai.set_connect_async(True)
bonsai.set_debug(True)

LDAP_URL = "ldaps://ldaps.capable.lan"
CACERT_FILE = "ca.crt"

async def main():
    print("running")
    cli = bonsai.LDAPClient(LDAP_URL)
    cli.set_ca_cert(CACERT_FILE)
    async with cli.connect(is_async=True, timeout=5):
        pass

asyncio.run(main())

If I change bonsai.set_connect_async(True) to False the connection succeeds but the code blocks. Which causes problems in the app I am building when an ldap server becomes unreachable.

Changing the URL to ldap:// with bonsai.set_connect_async(True) works as expected.

The truncated debug output from running the example code is included below

Thanks!

DBG: ldapconnection_new [self:0x7f485f110648]
DBG: ldapconnection_init (self:0x7f485f110648)
DBG: ldapconnection_open (self:0x7f485f110648)
DBG: connecting (self:0x7f485f110648)
DBG: create_conn_info (mech:SIMPLE, sock:6, creds:0x7f486ce6e9f0)
DBG: ldapconnectiter_new [self:0x7f485ed4ed68]
DBG: create_init_thread_data (client:0x7f485ed449e8, sock:6)
DBG: create_init_thread (ld:0x559352f0bf40, info:0x5593530b5b00, thread:0)
DBG: ldap_init_thread_func (params:0x559352f0bf40)
DBG: set connecting async: 1
DBG: ldap_init_thread_func [retval:0]
DBG: ldapconnection_result (self:0x7f485f110648, args:0x7f485f8eafd0, kwds:(nil))[msgid:7]
DBG: LDAPConnection_Result (self:0x7f485f110648, msgid:7, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7f485ed4ed68, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:1, thread:139948802778880, timeout:-1, misc:0x559352f0bf40)
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x7f485ed4ed68)
DBG: binding [state:3]
DBG: _ldap_bind (ld:0x7f4858000b20, info:0x5593530b5b00, ppolicy:0, result:(nil), msgid:0)
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_result (self:0x7f485f110648, args:0x7f485f8eafd0, kwds:(nil))[msgid:7]
DBG: LDAPConnection_Result (self:0x7f485f110648, msgid:7, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7f485ed4ed68, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_result (self:0x7f485f110648, args:0x7f485f8eafd0, kwds:(nil))[msgid:7]
DBG: LDAPConnection_Result (self:0x7f485f110648, msgid:7, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7f485ed4ed68, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_result (self:0x7f485f110648, args:0x7f485f8eafd0, kwds:(nil))[msgid:7]
DBG: LDAPConnection_Result (self:0x7f485f110648, msgid:7, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7f485ed4ed68, timeout:-1) [tls:0, state:4]

... The binding to LDAPConnectIter_Next block repeats until the timeout is reached

Traceback (most recent call last):
  File "./bonsai_ldaps_hang.py", line 21, in <module>
    asyncio.run(main())
  File "/usr/lib64/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/lib64/python3.7/asyncio/base_events.py", line 584, in run_until_complete
    return future.result()
  File "./bonsai_ldaps_hang.py", line 18, in main
    async with cli.connect(is_async=True, timeout=5):
  File "bonsai/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 30, in __aenter__
    return await self.__open_coro
  File "bonsai/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 63, in _poll
    raise exc
  File "bonsai/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 59, in _poll
    return await asyncio.wait_for(fut, timeout)
  File "/usr/lib64/python3.7/asyncio/tasks.py", line 423, in wait_for
    raise futures.TimeoutError()
concurrent.futures._base.TimeoutError

async paged_search auto_page_acquire not working

When I try to run the paged search with the client connection as asynchronous I get all the results I'm expecting (1238)

def paged_search(*args, **kwargs):
    global client
    if not client:
        client = bonsai.LDAPClient(f"ldap://{settings['ldap']['host']}")
        client.set_server_chase_referrals(False)
        client.set_auto_page_acquire(True)
        client.set_credentials("SIMPLE", user=settings['ldap']['user'], 
                                        password=settings['ldap']['password'])
    final_result=[]
    logger.info(f"Connecting to ldap://{settings['ldap']['host']}...")
    with client.connect(is_async=False, timeout=settings['ldap']['timeout']) as conn:
        result = conn.paged_search(*args, **kwargs)
        for r in result:
            final_result.append(r)
        logger.debug(len(final_result))

    return final_result
# Output
# DEBUG:aiohttp.server:1238

However when I change the flags and functions to asynchronous I'm only getting the first page of results

async def paged_search(*args, **kwargs):
    global client
    if not client:
        client = bonsai.LDAPClient(f"ldap://{settings['ldap']['host']}")
        client.set_server_chase_referrals(False)
        client.set_auto_page_acquire(True)
        client.set_credentials("SIMPLE", user=settings['ldap']['user'], 
                                        password=settings['ldap']['password'])

    final_result=[]
    logger.info(f"Connecting to ldap://{settings['ldap']['host']}...")
    async with client.connect(is_async=True, timeout=settings['ldap']['timeout']) as conn:
        result = await conn.paged_search(*args, **kwargs)
        for r in result:
            final_result.append(r)
        logger.debug(len(final_result))

    return final_result
# OUTPUT
# DEBUG:aiohttp.server:1003

I suspect that it's only returning the first page of results and what I've also noticed is that I'm also getting an extra 3 empty entries which I haven't been able to track down as to why yet.

I did one last test based on a previous issue you resolved #31 where you turned off auto_page_acquire and used the function acquire_next_page and that seemed to work for me

async def paged_search(*args, **kwargs):
    global client
    if not client:
        client = bonsai.LDAPClient(f"ldap://{settings['ldap']['host']}")
        client.set_server_chase_referrals(False)
        client.set_auto_page_acquire(False)
        client.set_credentials("SIMPLE", user=settings['ldap']['user'], 
                                        password=settings['ldap']['password'])

    final_result=[]
    logger.info(f"Connecting to ldap://{settings['ldap']['host']}...")
    async with client.connect(is_async=True, timeout=settings['ldap']['timeout']) as conn:
        result = await conn.paged_search(*args, **kwargs)
        for r in result:
            final_result.append(r)
        logger.debug(len(final_result))
        msgid = result.acquire_next_page()
        while msgid is not None:
            result = await conn._evaluate(msgid)
            for r in result:
                final_result.append(r)
            msgid = result.acquire_next_page()
    logger.debug(len(final_result))
    return final_result
# Output
# DEBUG:aiohttp.server:1003
# DEBUG:aiohttp.server:1238

This was tested using bonsai 1.2.1 running in a docker container based on the image python:3.7.6-buster

How to get operational attributes

Hi again ☺️
Sorry for bothering. I am looking for a way to retrieve also operational attributes from a search. I specifically need for a way to obtain the values for memberOf associated to an entry that belongs to some groupOfNames.

Is there any way to do that easily?

Thanks again for this nice library!

Linux install issue. Need to use -std=c99 to compile

Hi,

Thanks for this module. On RedHat 6, I find that I need to use the -std=c99 compiler option otherwise I see this error doing when doing a 'pip install pyldap':

building '_ldap' extension gcc -pthread -Werror=declaration-after-statement -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -DHAVE_SASL -DHAVE_TLS -DHAVE_LIBLDAP_R -DHAVE_LIBLDAP_R -DLDAPMODULE_VERSION=2.4.20 -IModules -I/usr/include -I/usr/include/sasl -I/usr/local/include -I/usr/local/include/sasl -I/home/jspeno200/virtualenvs/dashboard/include -I/usr/local/include/python3.4m -c Modules/LDAPObject.c -o build/temp.linux-x86_64-3.4/Modules/LDAPObject.o In file included from Modules/LDAPObject.c:18: /usr/include/sasl/sasl.h:349: warning: function declaration isn’t a prototype Modules/LDAPObject.c: In function ‘l_ldap_result4’: Modules/LDAPObject.c:1023: error: ISO C90 forbids mixed declarations and code error: command 'gcc' failed with exit status 1

Is there something we can do to make installing work without needing the extra compiler flags? Please let me know how I can help.

True async?

Hi there,

Quick question, when I do async connect is it thread-pooled or is it going truly async?

async with client.connect(is_async=True) as conn:
        res = await conn.search("ou=nerdherd,dc=bonsai,dc=test", 2)
        print(res)
        who = await conn.whoami()
        print(who)

Thanks,
Roman

Conflict with pycurl (async code)

Hi!

Code:

    import asyncio
    import pycurl # code work whitout this import
    from bonsai import LDAPClient, LDAPSearchScope

    async def do():
        LDAP_SERVER = ''
        LDAP_USER = ''  
        LDAP_PASSWORD = ''

        client = LDAPClient(LDAP_SERVER)
        client.set_credentials("SIMPLE", (LDAP_USER, LDAP_PASSWORD))

        async with client.connect(is_async=True) as conn:
            await conn.search(
                base="cn=users_catalog,ou=users,dc=<DC>",
                scope=LDAPSearchScope.SUBTREE,
            )

    loop = asyncio.get_event_loop()
    loop.run_until_complete(do())

Output:

Assertion failed: (LDAP_VALID( ld )), function ldap_set_option, file options.c, line 456.

Platform:

$ python -V                                                                                                                            
Python 3.6.3
$ uname -a
Darwin MacBook-Pro-admin.local 17.0.0 Darwin Kernel Version 17.0.0: Thu Aug 24 21:48:19 PDT 2017; root:xnu-4570.1.46~2/RELEASE_X86_64 x86_64
$ clang -v                                                                                                                          
Apple LLVM version 9.0.0 (clang-900.0.38)
Target: x86_64-apple-darwin17.0.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

Mac mini M1 chip can't import

uname:

Darwin yangEverdeMac-mini.local 20.6.0 Darwin Kernel Version 20.6.0: Wed Jun 23 00:26:27 PDT 2021; root:xnu-7195.141.2~5/RELEASE_ARM64_T8101 arm64

python version

Python 3.8.11

bonsai version:

bonsai==1.2.1

openldap:

brew list openldap
/opt/homebrew/Cellar/openldap/2.5.6/.bottle/etc/ (37 files)
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapadd
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapcompare
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapdelete
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapexop
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapmodify
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapmodrdn
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldappasswd
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapsearch
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapurl
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapvc
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapwhoami
/opt/homebrew/Cellar/openldap/2.5.6/include/ (10 files)
/opt/homebrew/Cellar/openldap/2.5.6/lib/liblber-2.5.0.dylib
/opt/homebrew/Cellar/openldap/2.5.6/lib/libldap-2.5.0.dylib
/opt/homebrew/Cellar/openldap/2.5.6/lib/pkgconfig/ (2 files)
/opt/homebrew/Cellar/openldap/2.5.6/lib/ (4 other files)
/opt/homebrew/Cellar/openldap/2.5.6/libexec/slapd
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapacl
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapadd
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapauth
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapcat
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapdn
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapindex
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapmodify
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slappasswd
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapschema
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slaptest
/opt/homebrew/Cellar/openldap/2.5.6/share/man/ (257 files)

when import error:

import bonsai
Traceback (most recent call last):
File "", line 1, in
File "/Users/yangever/PycharmProjects/lpcmdb/venv/lib/python3.8/site-packages/bonsai/init.py", line 3, in
from .ldapconnection import LDAPConnection
File "/Users/yangever/PycharmProjects/lpcmdb/venv/lib/python3.8/site-packages/bonsai/ldapconnection.py", line 5, in
from ._bonsai import ldapconnection, ldapsearchiter
ImportError: dlopen(/Users/yangever/PycharmProjects/lpcmdb/venv/lib/python3.8/site-packages/bonsai/_bonsai.cpython-38-darwin.so, 2): Symbol not found: _ldap_create_passwordpolicy_control
Referenced from: /Users/yangever/PycharmProjects/lpcmdb/venv/lib/python3.8/site-packages/bonsai/_bonsai.cpython-38-darwin.so
Expected in: flat namespace
in /Users/yangever/PycharmProjects/lpcmdb/venv/lib/python3.8/site-packages/bonsai/_bonsai.cpython-38-darwin.so

when install whl

pip install ~/Downloads/bonsai-1.2.1-cp38-cp38-macosx_10_14_x86_64.whl

ERROR: bonsai-1.2.1-cp38-cp38-macosx_10_14_x86_64.whl is not a supported wheel on this platform.

final

maybe bonsai not support arm64? but i'am install the openldap, it shoud has lib/so ..etc requirements..

Question: Persistent Search and Extended Operations

I don't think persistent search is implemented in this library, nor a generic way to expose calling an arbitrary extended operation from python - would you be open to a pull request for those features?

If so, are there any requirements you have before you'd merge that you can think of off the top of your head, or should I take my best guess at the API for each feature, issue the request and just work from there?

Paging the result for attributes

I am trying to get members of a group and find their email address. The AD group contains 50k+ results. When I query the AD group I can only get 1500 results. I tried the acquire_next_page flag but couldn't get the next set of attributes.

Unable to set connect timeout

Hi,

The "timeout" parameter on LDAPClient.connect() seems to only affect the LDAP bind process, and not the network connection. So if the network connection times out (aka. SYN timeout) Bonsai seems to hang forever.

Example code:

import bonsai

def main():
    cli = bonsai.LDAPClient("ldap://bonsai.test.watn.no")
    print("Connecting...")
    cli.connect(timeout=2.0)
    print("Connected!")
    cli.close()

if __name__ == "__main__":
    main()

(bonsai.test.watn.no drops packets on port 389, so the connection will end in SYN timeout).

This also affect LDAPS connections if the SSL handshake times out. I'm testing on Linux, Fedora 27, with OpenLDAP 2.4.45-4.fc27.

(otherwise; great module! Thanks for making it.)

`modify_password` fails in asyncio when a new password is provided

Hi!

I am now trying to use modify_password in async mode, which works fine when no new password is provided.
Unfortunately, this raises a strange error in async mode:

Traceback (most recent call last):
  File "/home/lab/bonsai/update_me.py", line 60, in <module>
    asyncio.run(asynchronous(oldpass, newpass))
  File "/usr/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/home/lab/bonsai/update_me.py", line 42, in asynchronous
    res = await conn.modify_password(
  File "/home/lab/bonsai/venv/lib/python3.9/site-packages/bonsai/asyncio/aioconnection.py", line 60, in _poll
    raise exc
  File "/home/lab/bonsai/venv/lib/python3.9/site-packages/bonsai/asyncio/aioconnection.py", line 55, in _poll
    return await asyncio.wait_for(fut, timeout)
  File "/usr/lib/python3.9/asyncio/tasks.py", line 442, in wait_for
    return await fut
  File "/home/lab/bonsai/venv/lib/python3.9/site-packages/bonsai/asyncio/aioconnection.py", line 40, in _ready
    res = super().get_result(msg_id)
bonsai.errors.InvalidMessageID: Given message ID is invalid or the associated operation is already finished. (0xFF9C [-100])

When a new password is provided, the server is expected to return no data, which is handled here:

if (data == NULL) Py_RETURN_NONE;

In this situation, None is returned as the result to the python caller.

In async mode, a result None means that the caller has to try again later as described here:

if res is not None:

This can be fixed by returning True or any other value instead of None, but that would break the user API and existing applications.
On the other hand, rewriting the way these loops work may be a lot of work :-(.

Regards,

Romain

Is STARTTLS possible?

Hi, thanks for this nice library!
I'd like to use it with my project, but as far as I could get, there is no clear way to use STARTTLS mechanism. When creating an LDAPClient instance there is just a boolean tls parameter. Trying to connect to an OpenLDAP server which advertises itself on port 389 and requires confientiality, I get

LDAPError: Confidentiality required. confidentiality required (0x000D [13])

with a connection with tls=False. Trying to connecting with a connection with tls=True I get

ConnectionError: Can't contact LDAP server. (0xFFFF [-1])

I think because it tries to wrap the connection in a tls socket.

The only reference I found in this repo to STARTTLS is this commit (f91d203), but I don't get its meaning.

Can you help me understand it? Thanks!

Escape filter and dn

Is there some method in bonsai to escape characters when building filter or dn for search?

Getting ACL for servers

Is there a way to find ACL for AD servers to see what type of access do users have on the active directory server. So something equivalent to Get-ACL Powershell command. An example of details that are returned by this command screenshot -

image

Is there a way to pull this information using Bonsai?

ValueError('Invalid file descriptor: -1') raised when sever not reachable

Steps to reproduce:

call: await client.connect(is_async=True) on a server that is not reachable

Actual:

Traceback (most recent call last):
  File "/home/garyvdm/dev/fnb/bpm-ng/ve/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 60, in _poll
    return await asyncio.wait_for(fut, timeout)
  File "/usr/lib/python3.7/asyncio/tasks.py", line 388, in wait_for
    return await fut
  File "/home/garyvdm/dev/fnb/bpm-ng/ve/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 45, in _ready
    res = super().get_result(msg_id)
bonsai.errors.ConnectionError: Can't contact LDAP server. (0xFFFF [-1])

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/garyvdm/dev/fnb/bpm-ng/python/bpm/web_util.py", line 41, in rest_request_handler_inner
    result = await func(request)
  File "/home/garyvdm/dev/fnb/bpm-ng/python/bpm/web_app.py", line 92, in get_branches
    return await model.get_branches()
  File "/home/garyvdm/dev/fnb/bpm-ng/python/bpm/model/__init__.py", line 232, in get_branches
    async with self.connection_manager as connection:
  File "/home/garyvdm/dev/fnb/bpm-ng/python/bpm/model/__init__.py", line 75, in __aenter__
    self.connection = await self.client.connect(is_async=True)
  File "/home/garyvdm/dev/fnb/bpm-ng/ve/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 62, in _poll
    self._loop.remove_reader(self.fileno())
  File "uvloop/loop.pyx", line 2302, in uvloop.loop.Loop.remove_reader
  File "uvloop/loop.pyx", line 724, in uvloop.loop.Loop._remove_reader
  File "uvloop/loop.pyx", line 682, in uvloop.loop.Loop._fileobj_to_fd
ValueError: Invalid file descriptor: -1

As you can see, a bonsai.errors.ConnectionError but another error was raised in a handler of that exception.

Expected:

A bonsai.errors.ConnectionError is raised.

How to specify custom CA file

i am trying to connect to a ldap server that uses a certificated that is singed by a internal ca.

i tried to set the path to the CA file using "set_ca_cert_dir" like this.

client.set_ca_cert_dir('/path/to/cert/contrib/ca-bundle.pem')

or

client.set_ca_cert_dir('/path/to/cert/contrib/')

but the certificate is not picked up. any clue what needs to be done to get this working?

Unable to import on Mac OS X 10.10

Hi

This library looks pretty neat, but when i installed it and tried to import it on OS X i received the following import error:

Python 3.4.3 (default, Jul 13 2015, 12:18:23)
[GCC 4.2.1 Compatible Apple LLVM 6.1.0 (clang-602.0.53)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyldap
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/kradalby/git/garru/env/lib/python3.4/site-packages/pyldap/__init__.py", line 3, in <module>
    from pyldap.ldapconnection import LDAPConnection
  File "/Users/kradalby/git/garru/env/lib/python3.4/site-packages/pyldap/ldapconnection.py", line 1, in <module>
    from pyldap._cpyldap import _LDAPConnection
ImportError: dlopen(/Users/kradalby/git/garru/env/lib/python3.4/site-packages/pyldap/_cpyldap.so, 2): Symbol not found: _ldap_control_find
  Referenced from: /Users/kradalby/git/garru/env/lib/python3.4/site-packages/pyldap/_cpyldap.so
  Expected in: flat namespace
 in /Users/kradalby/git/garru/env/lib/python3.4/site-packages/pyldap/_cpyldap.so

I guess it may be a compile error?

OS X version is 10.10.4
Python 3.4.3

I have not installed any additional LDAP libs, or anything, this may be the problem?

Install command:

pip install git+https://github.com/Noirello/PyLDAP.git

I will be happy to provide any additional information if needed.

Support async/await syntax

Hi,

If we are talking about asyncio and Python 3.5+ we want to use async/await syntax instead of coroutine decorator and yield from.

Here an example how I see it:

import asyncio
import bonsai


async def do():
    client = bonsai.LDAPClient('...')
    client.set_credentials('SIMPLE', ('...', '...'))
    client.set_cert_policy('allow')

    async with client.connect(is_async=True, timeout=10.0) as conn:
        res = await conn.search('...', 2)
        print(res)
        who = await conn.whoami()
        print(who)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(do())
    loop.run_until_complete(do())

Is it possible?

Dead Project?

Sorry to create an issue just to ask this bit, has development stopped?

`modify_password` does not work with OpenLDAP when a new password is set

Hi!

While trying to use bonsai to change a password on a OpenLDAP server I encountered a weird failure.

From a python point of view I get the following backtrace:

(venv) $ python update_password.py
Traceback (most recent call last):
  File "/home/lab/update_password.py", line 18, in <module>
    res = conn.modify_password(
  File "/home/lab/venv/lib/python3.9/site-packages/bonsai/ldapconnection.py", line 394, in modify_password
    return super().modify_password(user, new_password, old_password, timeout)
  File "/home/lab/venv/lib/python3.9/site-packages/bonsai/ldapconnection.py", line 64, in modify_password
    return self._evaluate(
  File "/home/lab/venv/lib/python3.9/site-packages/bonsai/ldapconnection.py", line 246, in _evaluate
    return self.get_result(msg_id, timeout)
bonsai.errors.LDAPError: password hash failed. (0x0050 [80])

This test program close to no interrest here as many tests were performed to pinpoint the origin of this issue.
On the server side, nothing seems unusual except for these few lines in the (verbose) log:

Nov  1 22:21:44 ldap slapd[8174]: send_ldap_extended: err=80 oid= len=0
Nov  1 22:21:44 ldap slapd[8174]: send_ldap_response: msgid=2 tag=120 err=80
Nov  1 22:21:44 ldap slapd[8174]: conn=1015 op=1 RESULT oid= err=80 text=password hash failed

The server runs on Ubuntu 18.04.6 LTS with OpenLDAP 2.4.45+dfsg-1ubuntu1.10.
It uses and requires SSL, and the password strategy is so that it is stored hashed using sha512crypt.

I can already update passwords when using pam from another connected server on the network.
I can also successfully get a new password when new_password is not provided to modify_password,
in which case the new password is gathered from the server response.

After more digging it seems to come from the following line:

ber_printf(ber, "n}");

    ber_printf(ber, "n}");

When replaced by the following, everything now works as expected:

    ber_printf(ber, "}");

There were already some discussions on samba's bugtracker about this:
https://bugzilla.samba.org/show_bug.cgi?id=5886
This attachment caught my interest (fix for smbpasswd to work with OpenLDAP 2.4):
https://bugzilla.samba.org/attachment.cgi?id=5461

Their current implementation even removed the N:
https://github.com/samba-team/samba/blob/master/source3/passdb/pdb_ldap.c#L1762

Here is the same line for pam_ldap:
https://github.com/PADL/pam_ldap/blob/master/pam_ldap.c#L3267
And here is the line in OpenLDAP implementation:
https://git.openldap.org/openldap/openldap/-/blob/master/clients/tools/ldappasswd.c#L299

It seems like N does not work on windows as stated in commit 32f66de.
With OpenLDAP headers, this seems to be a debug modifier (ignored in production):
https://git.openldap.org/openldap/openldap/-/blob/master/libraries/liblber/encode.c#L553

Should this fix be working on windows, I suggest to use the fix mentioned earlier!
I will open a merge request attached to this issue.

Thanks!

Romain

Add support for async with

LDAPConnection needs to implement __aenter__ and __aexit__ methods to be able to use async with (as Issue #12 pointed it out).

Process finished with exit code 11

I recently got Macbook system refreshed and I am trying to get bonsai working -

openssl 1.1.1f
openldap 2.4.49
Python 3.7.7
Bonsai 1.2.0
import asyncio
import bonsai
CACERT_FILE = ".//cacerts.pem"
bonsai.set_connect_async(False)
bonsai.set_debug(True, -1)
server_name ="ldaps://server.com"
client = bonsai.LDAPClient(server_name)
client.set_ca_cert(CACERT_FILE)
client.set_credentials("SIMPLE", user=user, password=password)
client.set_auto_page_acquire(True)
base = "x,y,z"
async def get_last_login(search_filter):
    async with client.connect(is_async=True) as conn:
        res = await conn.search(base=base, scope=2, filter_exp=search_filter, attrlist=['*'])
    print(res[0])

if __name__ == '__main__':
    search_filter = "(&(objectClass=group)(samaccountname={}))".format('Test')
    print(search_filter)
    loop = asyncio.get_event_loop()
    loop.run_until_complete(get_last_login(search_filter))

ldap_url_parse_ext(ldap://localhost/)
ldap_init: trying /usr/local/etc/openldap/ldap.conf
ldap_init: using /usr/local/etc/openldap/ldap.conf
ldap_init: HOME env is /Users/MYUSER
ldap_init: trying /Users/MYUSER/ldaprc
ldap_init: trying /Users/MYUSER/.ldaprc
ldap_init: trying ldaprc
ldap_init: LDAPCONF env is NULL
ldap_init: LDAPRC env is NULL
(&(objectClass=group)(samaccountname=Test))
DBG: ldapconnection_new [self:0x10a2a8fa0]
DBG: ldapconnection_init (self:0x10a2a8fa0)
DBG: ldapconnection_open (self:0x10a2a8fa0)
DBG: connecting (self:0x10a2a8fa0)
DBG: create_conn_info (mech:SIMPLE, sock:7, creds:0x10a2c2780)
DBG: ldapconnectiter_new [self:0x10a2cf4e0]
DBG: create_init_thread_data (client:0x1063bba50, sock:7)
DBG: create_init_thread (ld:0x7ff42fc02400, info:0x7ff42fc1f290, thread:0)
DBG: ldap_init_thread_func (params:0x7ff42fc02400)
ldap_create
ldap_url_parse_ext(ldaps://server.com:636)
DBG: set connecting async: 0
DBG: ldap_init_thread_func [retval:0]
DBG: ldapconnection_fileno (self:0x10a2a8fa0)[desc:0, dummy]
DBG: ldapconnection_fileno (self:0x10a2a8fa0)[desc:0, dummy]
DBG: ldapconnection_fileno (self:0x10a2a8fa0)[desc:0, dummy]
DBG: ldapconnection_fileno (self:0x10a2a8fa0)[desc:0, dummy]
DBG: ldapconnection_result (self:0x10a2a8fa0, args:0x109e19f10, kwds:0x0)[msgid:8]
DBG: LDAPConnection_Result (self:0x10a2a8fa0, msgid:8, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x10a2cf4e0, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:1, thread:123145450450944, timeout:-1, misc:0x7ff42fc02400)
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x10a2cf4e0)

Process finished with exit code 11

I am not sure if it is version compatibility or something else, is there something that I need to change?

Bonsai sets "deleteoldrdn: 1" on rename

Hi. I'm not sure that it is but it seems so. So it raise exception about object class violation ("cn" needed) when I try to rename entry.

Could you please explain how to:

  1. Enable debug mode to see bonsai modification log.
  2. Set "deleteoldrdn: 0" when I rename entry.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.