Complex LDAP Queries With ldapsearch and python-ldap


1 minute read

I recently needed to test a complex LDAP query in a Python script I was writing. I realized shortly after beginning to construct the query that I had never done anything more than a simple a=b or a=b* before, (where * is the wildcard). The solution is rather simple, albeit awkward due to the position of the binary operators. You’ll notice in the examples below that the operators are positioned before each operand. So rather than a & b, you would write &(a)(b).

To test the query I used the trusty ldapsearch utility. For example, to find the email addresses of all users in the “Example” OU that 1) have a nickname beginning with “M”, 2) are full time employees, and 3) their department number doesn’t start with “5” or their login shell is /bin/bash, use the following command:

ldapsearch -LLL -x -H ldap://ldap.example.com -b "ou=Example,dc=example,dc=com" '(&(nickname=M*)(employeeType=fulltime)(|(!(departmentNumber=5*))(loginShell=/bin/bash)))' mail | awk '/mail: / { print $2 }'

To perform the same query in Python, try the following:

import ldap
import sys
 
LDAP_URI = 'ldap://ldap.example.com'
SEARCH_BASE = 'ou=Example,dc=example,dc=com'
QUERY = '(&(nickname=M*)(employeeType=fulltime)(|(!(departmentNumber=5*))(loginShell=/bin/bash)))'
 
 
def ldap_search(ldap_uri, base, query):
  '''
  Perform an LDAP query.
  '''
    emails = []
 
    try:
        l = ldap.initialize(ldap_uri)
        l.protocol_version = ldap.VERSION3
 
        search_scope = ldap.SCOPE_SUBTREE
        retrieve_attributes = None
 
        ldap_result_id = l.search(
            base,
            search_scope,
            query,
            retrieve_attributes
        )
        result_set = []
        while 1:
            result_type, result_data = l.result(ldap_result_id, 0)
            if (result_data == []):
                break
            else:
                if result_type == ldap.RES_SEARCH_ENTRY:
                    result_set.append(result_data)
 
        if len(result_set) == 0:
            print('No results found.')
            return
 
        count = 0
        for i in range(len(result_set)):
            for entry in result_set[i]:
                try:
                    email = entry[1]['mail'][0]
                    count += 1
                    emails.append(email)
                except:
                    pass
 
    except ldap.LDAPError, e:
        print('LDAPError: %s.' % e)
 
    finally:
        l.unbind_s()
        print(emails)
 
 
def main():
    ldap_search(LDAP_URI, SEARCH_BASE, QUERY)
 
 
if __name__ == '__main__':
    sys.exit(main())

Happy LDAPing.