Skip to content

Instantly share code, notes, and snippets.

@liveaverage
Last active November 16, 2021 16:06
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save liveaverage/4503265 to your computer and use it in GitHub Desktop.
Python alternative to getadsmtp.pl; Runs faster and performs the same AD query for valid SMTP recipients. Credits noted in the comments.
#!/usr/bin/env python
#Credits: Marc Smith, http://marcitland.blogspot.com/2011/02/python-active-directory-linux.html
# DarkPixel, https://github.com/darkpixel/scripts/blob/master/getadsmtp.py
# JR, http://liveaverage.com
import sys, ldap, argparse
import ldap.modlist as modlist
from ldap.controls import SimplePagedResultsControl
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, description="Retrieve e-mail addresses from an LDAP server in postfix format")
parser.add_argument('-c', '--connect', required=True, action='store', help='The host to connect to (AD/Exchange Server)')
parser.add_argument('-r', '--port', action='store', help='Port to use for connecting, defaults to 636')
parser.add_argument('-u', '--user', action='store', required=True, help='Username to use (either cn=blah,dc=cust,dc=local or blah@cust.local format)')
parser.add_argument('-p', '--password', action='store', required=True, help='Password')
parser.add_argument('-o', '--ou', action='store', required=True, help='Org Unit (Base DN) to export from')
arg = parser.parse_args()
LDAP_SERVER = 'ldaps://%s:%s' %(arg.connect, arg.port or '3269')
BIND_DN = arg.user
BIND_PASS = arg.password
USER_FILTER = "(& (mailnickname=*) (| (objectClass=publicFolder)(&(objectCategory=person)(objectClass=user)(!(homeMDB=*))(!(msExchHomeServerName=*)))(&(objectCategory=person)(objectClass=user)(|(homeMDB=*)(msExchHomeServerName=*)))(objectCategory=person)(objectCategory=group)(objectClass=msExchDynamicDistributionList) ) (!(objectClass=contact)) )"
USER_BASE = arg.ou
PAGE_SIZE = 500
# LDAP connection
try:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
ldap_connection = ldap.initialize(LDAP_SERVER)
ldap_connection.set_option(ldap.OPT_REFERRALS, 0)
ldap_connection.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
ldap_connection.set_option(ldap.OPT_X_TLS,ldap.OPT_X_TLS_DEMAND)
ldap_connection.set_option( ldap.OPT_X_TLS_DEMAND, True )
ldap_connection.set_option( ldap.OPT_DEBUG_LEVEL, 255 )
ldap_connection.simple_bind_s(BIND_DN, BIND_PASS)
except ldap.LDAPError, e:
sys.stderr.write('Error connecting to LDAP server: ' + str(e) + '\n')
sys.exit(1)
# Lookup usernames from LDAP via paged search
paged_results_control = SimplePagedResultsControl(
ldap.LDAP_CONTROL_PAGE_OID, True, (PAGE_SIZE, ''))
accounts = []
pages = 0
while True:
serverctrls = [paged_results_control]
try:
msgid = ldap_connection.search_ext(USER_BASE,
ldap.SCOPE_SUBTREE,
USER_FILTER,
attrlist=['proxyAddresses'],
serverctrls=serverctrls)
except ldap.LDAPError, e:
sys.stderr.write('Error performing user paged search: ' +
str(e) + '\n')
sys.exit(1)
try:
unused_code, results, unused_msgid, serverctrls = \
ldap_connection.result3(msgid)
except ldap.LDAPError, e:
sys.stderr.write('Error getting user paged search results: ' +
str(e) + '\n')
sys.exit(1)
for result in results:
pages += 1
accounts.append(result)
cookie = None
for serverctrl in serverctrls:
if serverctrl.controlType == ldap.LDAP_CONTROL_PAGE_OID:
unused_est, cookie = serverctrl.controlValue
if cookie:
paged_results_control.controlValue = (PAGE_SIZE, cookie)
break
if not cookie:
break
# LDAP unbind
ldap_connection.unbind_s()
#Address Counter:
ac = 0
for entry in accounts:
#DEBUG: Convert entry to string and print:
#print "%s" % str(entry)
if hasattr(entry[1], 'has_key') and entry[1].has_key('proxyAddresses'):
addresses = entry[1]['proxyAddresses']
for addr in addresses:
if 'smtp:' in addr.lower():
ac += 1
print "%s OK" % (addr.lower().split('smtp:')[1])
#DEBUG: Convert addr to string and print:
#print "%s" % str(addr)
print "# %d results" % len(accounts)
#print "%d proxyAddresses" % ac
@liveaverage
Copy link
Author

Revised the original script's LDAP filter to include Public Folders when scraping Exchange for valid proxyAddresses. I found out the hard way it wasn't adding PFs with associated mail addresses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment