-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathldapquery.py
executable file
·119 lines (95 loc) · 4.71 KB
/
ldapquery.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/usr/bin/env python
from splunklib.searchcommands import dispatch, GeneratingCommand, Configuration, Option, validators
import sys
import os
import ldap
@Configuration(type='reporting')
class LDAPQueryCommand(GeneratingCommand):
uri = Option(require=True)
verifycert = Option(require=False, validate=validators.Boolean())
scope = Option(require=False)
basedn = Option(require=True)
binddn = Option(require=False)
bindpassword = Option(require=False)
ldapfilter = Option(require=False)
attributelist = Option(require=False)
def generate(self):
# Phase 1: Preparation
if not (self.uri[:7] == "ldap://" or self.uri[:8] == "ldaps://"):
raise Exception("Please specify the protocol ldap or ldaps in the uri.")
if self.scope:
if self.scope == "base":
self.scope = ldap.SCOPE_BASE
elif self.scope == "onelevel":
self.scope = ldap.SCOPE_ONELEVEL
elif self.scope == "subtree":
self.scope = ldap.SCOPE_SUBTREE
else:
raise Exception("Invalid scope filter given. Please use: base, onelevel, subtree")
else:
self.scope = ldap.SCOPE_SUBTREE
if not self.ldapfilter:
self.ldapfilter = "(objectClass=*)"
if self.attributelist:
self.attributelist = str(self.attributelist).split()
else:
self.attributelist = None
# Phase 2: Connect to LDAP server and run search
l = ldap.initialize(str(self.uri))
try:
if self.verifycert:
l.set_option(ldap.OPT_X_TLS_DEMAND, self.verifycert)
except:
pass
if self.binddn:
if self.bindpassword:
try:
l.simple_bind_s(self.binddn, self.bindpassword)
except ldap.INVALID_CREDENTIALS:
raise Exception("Credentials provided failed to bind to directory.")
except:
raise Exception("Failed to bind to directory. Please check arguments provided and network connectivity.")
else:
raise Exception("Please provide bindpassword argument for authenticated binds.")
try:
# run the search
result_id = l.search(self.basedn, self.scope, self.ldapfilter, self.attributelist)
except ldap.LDAPError as error:
if type(error.message) == dict and error.message.has_key('desc'):
raise Exception(str(error.message['desc']))
else:
raise Exception(str(error))
except:
raise Exception("LDAP query failed. Please check arguments provided and network connectivity.")
# Phase 3: Send results to Splunk
# When providing large datasets to Splunk, if Splunk determines certain fields are uncommon (which is likely with sparsely populated directory attributes), it will drop them resulting in an incomplete dataset for the user, so we take two passes here over the returned results to prevent fields being dropped. The first pass caches the results and determines all the attributes in the result set. The second pass adds any missing fields to all the results before sending to Splunk.
cache = []
attributes = set()
# results are returned asynchronously so we need to loop until no more entries are provided
while True:
result_type, result_data = l.result(result_id, 0)
if result_type == ldap.RES_SEARCH_ENTRY:
# for future reference the entries used below looks like this: [('uid=username,ou=users,dc=example,dc=com', {'cn': ['Example'], 'mail': ['username@example.com']})]
entry = result_data[0][1]
for attribute in list(entry):
if isinstance(entry[attribute], list):
utf8_list = []
for item in entry[attribute]:
utf8_list += [item.decode('UTF-8', "ignore")]
entry[attribute] = utf8_list
else:
entry[attribute] = entry[attribute].decode('UTF-8', "ignore")
entry["dn"] = result_data[0][0]
attributes.update(entry.keys())
cache += [entry]
else:
break
if self.binddn:
l.unbind_s()
# add any missing fields to the results
for entry in cache:
for attribute in attributes:
if attribute not in entry:
entry[attribute] = []
yield entry
dispatch(LDAPQueryCommand, sys.argv, sys.stdin, sys.stdout, __name__)