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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
import re
import os, os.path
# regex that matches a BIND request line
regex_num = r'[-]?\d+' # matches numbers including negative
regex_new_conn = re.compile(r'^(\[.+\]) (conn=%s) (fd=%s) (slot=%s) (?:SSL )?connection from (\S+)' % (regex_num, regex_num, regex_num))
regex_sslinfo = re.compile(r'^\[.+\] (conn=%s) SSL (.+)$' % regex_num)
regex_bind_req = re.compile(r'^(\[.+\]) (conn=%s) (op=%s) BIND dn=(.+) method=(\S+) version=\d ?(?:mech=(\S+))?' % (regex_num, regex_num))
regex_bind_res = re.compile(r'^(\[.+\]) (conn=%s) (op=%s) RESULT err=(%s) tag=97 ' % (regex_num, regex_num, regex_num))
regex_unbind = re.compile(r'^\[.+\] (conn=%s) op=%s UNBIND' % (regex_num, regex_num))
regex_closed = re.compile(r'^(\[.+\]) (conn=%s) (op=%s) fd=%s closed' % (regex_num, regex_num, regex_num))
regex_ssl_map_fail = re.compile(r'^\[.+\] (conn=%s) (SSL failed to map client certificate.*)$' % regex_num)
# bind errors we can ignore
ignore_errors = {'0': 'Success',
'10': 'Referral',
'14': 'SASL Bind In Progress'
}
REQ = 0
RES = 1
class Conn:
def __init__(self, timestamp, conn, fd, slot, ip):
self.conn = conn
self.fd = fd
self.slot = slot
self.ip = ip
self.timestamp = timestamp
self.ops = {}
self.sslinfo = ''
def addssl(self, sslinfo):
if self.sslinfo and sslinfo:
self.sslinfo += ' '
self.sslinfo += sslinfo
def addreq(self, timestamp, opnum, dn, method, mech='SIMPLE'):
retval = None
if opnum in self.ops: # result came before request?
op = self.ops.pop(opnum) # grab the op and remove from list
if op[RES]['errnum'] in ignore_errors: # don't care about this op
return retval
if not mech: mech = "SIMPLE"
op[REQ] = {'dn': dn, 'method': method, 'timestamp': timestamp,
'mech': mech}
retval = self.logstr(opnum, op)
else: # store request until we get the result
op = [None, None] # new empty list
if not mech: mech = "SIMPLE"
op[REQ] = {'dn': dn, 'method': method, 'timestamp': timestamp,
'mech': mech}
self.ops[opnum] = op
return retval
def addres(self, timestamp, opnum, errnum):
retval = None
if opnum in self.ops:
op = self.ops.pop(opnum) # grab the op and remove from list
if errnum in ignore_errors: # don't care about this op
return retval
op[RES] = {'errnum': errnum, 'timestamp': timestamp}
retval = self.logstr(opnum, op)
else: # result came before request in access log - store until we find request
op = [None, None] # new empty list
op[RES] = {'errnum': errnum, 'timestamp': timestamp}
self.ops[opnum] = op
return retval
def logstr(self, opnum, op):
# timestamp connnum opnum err=X request timestamp dn=Y method=Z mech=W timestamp ip=ip
logstr = '%s %s %s err=%s REQUEST %s dn=%s method=%s mech=%s %s ip=%s extra=%s' % (
op[RES]['timestamp'], self.conn, opnum, op[RES]['errnum'],
op[REQ]['timestamp'], op[REQ]['dn'], op[REQ]['method'], op[REQ]['mech'],
self.timestamp, self.ip, self.sslinfo
)
return logstr
# key is conn=X
# val is ops hash
# key is op=Y
# value is list
# list[0] is BIND request
# list[1] is RESULT
conns = {}
# file to log failed binds to
logf = None
def pre(plgargs):
global logf
logfile = plgargs.get('logfile', None)
if not logfile:
print "Error: missing required argument failedbinds.logfile"
return False
needchmod = False
if not os.path.isfile(logfile): needchmod = True
logf = open(logfile, 'a', 0) # 0 for unbuffered output
if needchmod: os.chmod(logfile, 0600)
return True
def post():
global logf
logf.close()
logf = None
def plugin(line):
# is this a new conn line?
match = regex_new_conn.match(line)
if match:
(timestamp, connid, fdid, slotid, ip) = match.groups()
if connid in conns: conns.pop(connid) # remove old one, if any
conn = Conn(timestamp, connid, fdid, slotid, ip)
conns[connid] = conn
return True
# is this an UNBIND line?
match = regex_unbind.match(line)
if match:
connid = match.group(1)
if connid in conns: conns.pop(connid) # remove it
return True
# is this a closed line?
match = regex_closed.match(line)
if match:
(timestamp, connid, opid) = match.groups()
if connid in conns: conns.pop(connid) # remove it
return True
# is this an SSL info line?
match = regex_sslinfo.match(line)
if match:
(connid, sslinfo) = match.groups()
if connid in conns:
conns[connid].addssl(sslinfo)
return True
# is this a line with extra SSL mapping info?
match = regex_ssl_map_fail.match(line)
if match:
(connid, sslinfo) = match.groups()
if connid in conns:
conns[connid].addssl(sslinfo)
return True
# is this a REQUEST line?
match = regex_bind_req.match(line)
if match:
(timestamp, connid, opnum, dn, method, mech) = match.groups()
# should have seen new conn line - if not, have to create a dummy one
conn = conns.get(connid, Conn('unknown', connid, '', '', 'unknown'))
logmsg = conn.addreq(timestamp, opnum, dn, method, mech)
if logmsg:
logf.write(logmsg + "\n")
return True
# is this a RESULT line?
match = regex_bind_res.match(line)
if match:
(timestamp, connid, opnum, errnum) = match.groups()
# should have seen new conn line - if not, have to create a dummy one
conn = conns.get(connid, Conn('unknown', connid, '', '', 'unknown'))
logmsg = conn.addres(timestamp, opnum, errnum)
if logmsg:
logf.write(logmsg + "\n")
return True
return True # no match
|