Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save david-batranu/66fb6bfca323cec1e577b9374c566a0a to your computer and use it in GitHub Desktop.
Save david-batranu/66fb6bfca323cec1e577b9374c566a0a to your computer and use it in GitHub Desktop.
This snippet allows you to do a Python LDAP search with paged controls. The latest version now supports Python "ldap" 2.4. Many thanks to Ilya Rumyantsev for doing the 2.4 legwork.
#! /usr/bin/python
import sys
import ldap
from ldap.controls import SimplePagedResultsControl
from distutils.version import LooseVersion
# Check if we're using the Python "ldap" 2.4 or greater API
LDAP24API = LooseVersion(ldap.__version__) >= LooseVersion('2.4')
# If you're talking to LDAP, you should be using LDAPS for security!
LDAPSERVER='ldaps://ldap.somecompany.com'
BASEDN='cn=users,dc=somecompany,dc=com'
LDAPUSER = 'uid=someuser,dc=somecompany,dc=com'
LDAPPASSWORD = 'somepassword'
PAGESIZE = 1000
ATTRLIST = ['uid', 'shadowLastChange', 'shadowMax', 'shadowExpire']
SEARCHFILTER='uid=*'
def create_controls(pagesize):
"""Create an LDAP control with a page size of "pagesize"."""
# Initialize the LDAP controls for paging. Note that we pass ''
# for the cookie because on first iteration, it starts out empty.
if LDAP24API:
return SimplePagedResultsControl(True, size=pagesize, cookie='')
else:
return SimplePagedResultsControl(ldap.LDAP_CONTROL_PAGE_OID, True,
(pagesize,''))
def get_pctrls(serverctrls):
"""Lookup an LDAP paged control object from the returned controls."""
# Look through the returned controls and find the page controls.
# This will also have our returned cookie which we need to make
# the next search request.
if LDAP24API:
return [c for c in serverctrls
if c.controlType == SimplePagedResultsControl.controlType]
else:
return [c for c in serverctrls
if c.controlType == ldap.LDAP_CONTROL_PAGE_OID]
def set_cookie(lc_object, pctrls, pagesize):
"""Push latest cookie back into the page control."""
if LDAP24API:
cookie = pctrls[0].cookie
lc_object.cookie = cookie
return cookie
else:
est, cookie = pctrls[0].controlValue
lc_object.controlValue = (pagesize,cookie)
return cookie
# This is essentially a placeholder callback function. You would do your real
# work inside of this. Really this should be all abstracted into a generator...
def process_entry(dn, attrs):
"""Process an entry. The two arguments passed are the DN and
a dictionary of attributes."""
print dn, attrs
# Ignore server side certificate errors (assumes using LDAPS and
# self-signed cert). Not necessary if not LDAPS or it's signed by
# a real CA.
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
# Don't follow referrals
ldap.set_option(ldap.OPT_REFERRALS, 0)
l = ldap.initialize(LDAPSERVER)
l.protocol_version = 3 # Paged results only apply to LDAP v3
try:
l.simple_bind_s(LDAPUSER, LDAPPASSWORD)
except ldap.LDAPError as e:
exit('LDAP bind failed: %s' % e)
# Create the page control to work from
lc = create_controls(PAGESIZE)
# Do searches until we run out of "pages" to get from
# the LDAP server.
while True:
# Send search request
try:
# If you leave out the ATTRLIST it'll return all attributes
# which you have permissions to access. You may want to adjust
# the scope level as well (perhaps "ldap.SCOPE_SUBTREE", but
# it can reduce performance if you don't need it).
msgid = l.search_ext(BASEDN, ldap.SCOPE_ONELEVEL, SEARCHFILTER,
ATTRLIST, serverctrls=[lc])
except ldap.LDAPError as e:
sys.exit('LDAP search failed: %s' % e)
# Pull the results from the search request
try:
rtype, rdata, rmsgid, serverctrls = l.result3(msgid)
except ldap.LDAPError as e:
sys.exit('Could not pull LDAP results: %s' % e)
# Each "rdata" is a tuple of the form (dn, attrs), where dn is
# a string containing the DN (distinguished name) of the entry,
# and attrs is a dictionary containing the attributes associated
# with the entry. The keys of attrs are strings, and the associated
# values are lists of strings.
for dn, attrs in rdata:
process_entry(dn, attrs)
# Get cookie for next request
pctrls = get_pctrls(serverctrls)
if not pctrls:
print >> sys.stderr, 'Warning: Server ignores RFC 2696 control.'
break
# Ok, we did find the page control, yank the cookie from it and
# insert it into the control for our next search. If however there
# is no cookie, we are done!
cookie = set_cookie(lc, pctrls, PAGESIZE)
if not cookie:
break
# Clean up
l.unbind()
# Done!
sys.exit(0)
#
# This code is in many ways a refactoring of code found at the following sites:
#
# http://www.novell.com/coolsolutions/tip/18274.html
# https://travelingfrontiers.wordpress.com/2013/04/05/how-to-use-python-ldap-paged-results-control-to-handle-large-ldap-searches
#
# More about this code can be found here:
#
# http://mattfahrner.com/2014/03/09/using-paged-controls-with-python-and-ldap/
#
import sys
import ldap
# If you're talking to LDAP, you should be using LDAPS for security!
LDAPSERVER='ldaps://ldap.somecompany.com'
BASEDN='cn=users,dc=somecompany,dc=com'
LDAPUSER = 'uid=someuser,dc=somecompany,dc=com'
LDAPPASSWORD = 'somepassword'
PAGESIZE = 1000
ATTRLIST = ['uid', 'shadowLastChange', 'shadowMax', 'shadowExpire']
SEARCHFILTER='uid=*'
# Ignore server side certificate errors (assumes using LDAPS and
# self-signed cert). Not necessary if not LDAPS or it's signed by
# a real CA.
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
# Don't follow referrals
ldap.set_option(ldap.OPT_REFERRALS, 0)
l = ldap.initialize(LDAPSERVER)
l.protocol_version = 3 # Paged results only apply to LDAP v3
try:
l.simple_bind_s(LDAPUSER, LDAPPASSWORD)
except ldap.LDAPError as e:
exit('LDAP bind failed: %s' % e)
# Initialize the LDAP controls for paging. Note that we pass ''
# for the cookie because on first iteration, it starts out empty.
lc = ldap.controls.SimplePagedResultsControl(ldap.LDAP_CONTROL_PAGE_OID, True,
(PAGESIZE,''))
# This is essentially a placeholder callback function. You would do your real
# work inside of this. Really this should be all abstracted into a generator...
def process_entry(dn, attrs):
"""Process an entry. The two arguments passed are the DN and
a dictionary of attributes."""
print dn, attrs
# Do searches until we run out of "pages" to get from
# the LDAP server.
while True:
# Send search request
try:
# If you leave out the ATTRLIST it'll return all attributes
# which you have permissions to access. You may want to adjust
# the scope level as well (perhaps "ldap.SCOPE_SUBTREE", but
# it can reduce performance if you don't need it).
msgid = l.search_ext(BASEDN, ldap.SCOPE_ONELEVEL, SEARCHFILTER,
ATTRLIST, serverctrls=[lc])
except ldap.LDAPError as e:
sys.exit('LDAP search failed: %s' % e)
# Pull the results from the search request
try:
rtype, rdata, rmsgid, serverctrls = l.result3(msgid)
except ldap.LDAPError as e:
sys.exit('Could not pull LDAP results: %s' % e)
# Each "rdata" is a tuple of the form (dn, attrs), where dn is
# a string containing the DN (distinguished name) of the entry,
# and attrs is a dictionary containing the attributes associated
# with the entry. The keys of attrs are strings, and the associated
# values are lists of strings.
for dn, attrs in rdata:
process_entry()
# Look through the returned controls and find the page controls.
# This will also have our returned cookie which we need to make
# the next search request.
pctrls = [
c for c in serverctrls if c.controlType == ldap.LDAP_CONTROL_PAGE_OID
]
if not pctrls:
print >> sys.stderr, 'Warning: Server ignores RFC 2696 control.'
break
# Ok, we did find the page control, yank the cookie from it and
# insert it into the control for our next search. If however there
# is no cookie, we are done!
est, cookie = pctrls[0].controlValue
if not cookie:
break
lc.controlValue = (page_size,cookie)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment