crunchtools 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crunchtools/CrunchLog.py +793 -0
- crunchtools/Filter.py +73 -0
- crunchtools/LogGraph.py +543 -0
- crunchtools/LogHash.py +408 -0
- crunchtools/ScriptLog.py +241 -0
- crunchtools/__init__.py +51 -0
- crunchtools/api.py +122 -0
- crunchtools/cli.py +463 -0
- crunchtools/data/filters/daemon.stopwords +6 -0
- crunchtools/data/filters/hash.stopwords +10 -0
- crunchtools/data/filters/host.stopwords +1 -0
- crunchtools/data/filters/words.stopwords +204 -0
- crunchtools/data/fingerprint_library/fedora11-reboot-dell.fp +650 -0
- crunchtools/data/fingerprint_library/rhel4-reboot-dl380.fp +654 -0
- crunchtools/data/fingerprint_library/rhel4-reboot-vmware.fp +309 -0
- crunchtools/data/fingerprint_library/rhel5-reboot-dl380.fp +336 -0
- crunchtools/data/fingerprint_library/rhel5-reboot-vmware.fp +268 -0
- crunchtools/data/fingerprint_library/ubuntu10.04-reboot-kvm.fp +330 -0
- crunchtools/data/fingerprint_library/ubuntu9.04-reboot-vmware.fp +359 -0
- crunchtools/data/fingerprints/fedora11-reboot.fp +650 -0
- crunchtools/data/fingerprints/rhel4-reboot.fp +966 -0
- crunchtools/data/fingerprints/rhel5-reboot.fp +604 -0
- crunchtools/data/fingerprints/ubuntu10.04-reboot.fp +330 -0
- crunchtools/data/fingerprints/ubuntu9.04-reboot.fp +359 -0
- crunchtools/errors.py +28 -0
- crunchtools/resources.py +58 -0
- crunchtools-2.1.0.dist-info/METADATA +165 -0
- crunchtools-2.1.0.dist-info/RECORD +31 -0
- crunchtools-2.1.0.dist-info/WHEEL +4 -0
- crunchtools-2.1.0.dist-info/entry_points.txt +2 -0
- crunchtools-2.1.0.dist-info/licenses/COPYING +624 -0
crunchtools/CrunchLog.py
ADDED
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generic log class which contains a payload of objects which conform to the
|
|
3
|
+
LogEntry specification. Log, which is a List (array) of type LogEntry, is
|
|
4
|
+
relied upon and consumed to build any of the XHash objects such as SuperHash
|
|
5
|
+
or GraphHash.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections import UserList
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
import logging
|
|
13
|
+
from .errors import EmptyLogError, ParseError
|
|
14
|
+
from random import choice
|
|
15
|
+
import datetime
|
|
16
|
+
import time
|
|
17
|
+
import types
|
|
18
|
+
#import rpdb2; rpdb2.start_embedded_debugger("password")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Bound on how many times select() will resample before giving up and
|
|
22
|
+
# using RawEntry. Without a bound, input that no driver claims spins forever.
|
|
23
|
+
MAX_SELECT_ROUNDS = 5
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Tally():
|
|
27
|
+
|
|
28
|
+
matrix = {}
|
|
29
|
+
tally_threshold = 0
|
|
30
|
+
|
|
31
|
+
def __init__(self, entry_types, max_sample_lines):
|
|
32
|
+
|
|
33
|
+
self.matrix = {}
|
|
34
|
+
self.max_sample_lines = max_sample_lines
|
|
35
|
+
self.tally_threshold = max_sample_lines / 4
|
|
36
|
+
|
|
37
|
+
for entry_type in entry_types:
|
|
38
|
+
self.matrix[entry_type] = 0
|
|
39
|
+
|
|
40
|
+
def append(self, entry_type):
|
|
41
|
+
self.matrix[entry_type] += 1
|
|
42
|
+
|
|
43
|
+
def is_type(self, entry_type):
|
|
44
|
+
|
|
45
|
+
# Setup the correct tally logic method
|
|
46
|
+
tally_logic = entry_type.tally_logic
|
|
47
|
+
|
|
48
|
+
m = self.matrix[entry_type]
|
|
49
|
+
th = self.tally_threshold
|
|
50
|
+
msl = self.max_sample_lines
|
|
51
|
+
|
|
52
|
+
if tally_logic(m, th, msl):
|
|
53
|
+
return True
|
|
54
|
+
else:
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CrunchLog(UserList):
|
|
59
|
+
"""
|
|
60
|
+
Class which extends UserList to provide robust in memory log object
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, filename=""):
|
|
64
|
+
UserList.__init__(self)
|
|
65
|
+
|
|
66
|
+
if filename == "":
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
if filename == "__none__":
|
|
70
|
+
buf = sys.stdin.readlines()
|
|
71
|
+
else:
|
|
72
|
+
logging.debug("Opening File: %s", filename)
|
|
73
|
+
with open(filename) as handle:
|
|
74
|
+
buf = handle.readlines()
|
|
75
|
+
|
|
76
|
+
self._build(buf, filename)
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_text(cls, text, source_name="<text>"):
|
|
80
|
+
"""Build a log from a string already in memory.
|
|
81
|
+
|
|
82
|
+
The reason this exists: every caller that is not a shell has its
|
|
83
|
+
payload in memory already, and the file-only constructor forced it
|
|
84
|
+
through a temporary file to use this library at all.
|
|
85
|
+
"""
|
|
86
|
+
log = cls()
|
|
87
|
+
log._build(text.splitlines(keepends=True), source_name)
|
|
88
|
+
return log
|
|
89
|
+
|
|
90
|
+
def _build(self, buf, source_name):
|
|
91
|
+
"""Select a driver for the buffer and parse every line with it."""
|
|
92
|
+
if len(buf) < 1:
|
|
93
|
+
raise EmptyLogError("no data found in " + (source_name or "input"))
|
|
94
|
+
|
|
95
|
+
# Automatically select entry type
|
|
96
|
+
self.Entry = self.select(buf)
|
|
97
|
+
|
|
98
|
+
# Save for introspective purpose
|
|
99
|
+
self.payload_type = self.Entry.__name__
|
|
100
|
+
self.file_name = source_name
|
|
101
|
+
self.build_date = datetime.datetime.now()
|
|
102
|
+
|
|
103
|
+
# Build from entry type
|
|
104
|
+
counter = 0
|
|
105
|
+
for line in buf:
|
|
106
|
+
try:
|
|
107
|
+
self.append(self.Entry(line))
|
|
108
|
+
counter += 1
|
|
109
|
+
except (ValueError, TypeError) as exc:
|
|
110
|
+
raise ParseError(counter, line) from exc
|
|
111
|
+
|
|
112
|
+
def select(self, buf):
|
|
113
|
+
"""
|
|
114
|
+
Determines which type of entry to use when building CrunchLog by
|
|
115
|
+
by sampling the buffer and using a quarum based on votes for each
|
|
116
|
+
log type
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
sample_lines = []
|
|
120
|
+
max_sample_lines = 10
|
|
121
|
+
t = Tally(entry_types, max_sample_lines)
|
|
122
|
+
|
|
123
|
+
if len(buf) < 1:
|
|
124
|
+
return RawEntry
|
|
125
|
+
|
|
126
|
+
# This loop used to be `while (1)`, which never terminated when no
|
|
127
|
+
# driver reached quorum — and sample_lines grew on every pass, so it
|
|
128
|
+
# burned memory while it span. A buffer of blank lines does exactly
|
|
129
|
+
# that: `choice(buf).split()` yields [], and every is_type() rejects
|
|
130
|
+
# an empty list, RawEntry's included, so nothing ever votes.
|
|
131
|
+
#
|
|
132
|
+
# Resampling more than a few times means the buffer is not giving a
|
|
133
|
+
# clear answer, and more rounds will not change that. Cap it and fall
|
|
134
|
+
# back to RawEntry, which is what the registry already appoints as the
|
|
135
|
+
# last resort.
|
|
136
|
+
for _ in range(MAX_SELECT_ROUNDS):
|
|
137
|
+
|
|
138
|
+
# Get X number of samples
|
|
139
|
+
for i in range(0, max_sample_lines):
|
|
140
|
+
sample_lines.append(choice(buf).split())
|
|
141
|
+
|
|
142
|
+
# Build tallies for the collected samples
|
|
143
|
+
for line in sample_lines:
|
|
144
|
+
for entry_type in entry_types:
|
|
145
|
+
if entry_type.is_type(line):
|
|
146
|
+
t.append(entry_type)
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
# Tally logic is determined by driver
|
|
150
|
+
for entry_type in entry_types:
|
|
151
|
+
if t.is_type(entry_type):
|
|
152
|
+
logging.info("Determined %s: %s", entry_type.__name__, t.matrix[entry_type])
|
|
153
|
+
|
|
154
|
+
return entry_type
|
|
155
|
+
|
|
156
|
+
logging.info("No driver reached quorum after %d rounds; using RawEntry",
|
|
157
|
+
MAX_SELECT_ROUNDS)
|
|
158
|
+
return RawEntry
|
|
159
|
+
|
|
160
|
+
def contains(self, obj):
|
|
161
|
+
"""Determine what kind of objects are contained in this Log"""
|
|
162
|
+
if len(self) >= 1:
|
|
163
|
+
return isinstance(self[len(self) - 1], obj)
|
|
164
|
+
else:
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
def display(self):
|
|
168
|
+
"""Simple display function to show entire log"""
|
|
169
|
+
for entry in self:
|
|
170
|
+
entry.display()
|
|
171
|
+
|
|
172
|
+
def subset(self, string):
|
|
173
|
+
"""Return Log object with subset of entries based on a filter"""
|
|
174
|
+
|
|
175
|
+
newlog = CrunchLog()
|
|
176
|
+
for entry in self:
|
|
177
|
+
if re.search(string, entry.log_entry):
|
|
178
|
+
newlog.append(entry)
|
|
179
|
+
|
|
180
|
+
return newlog
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class LogEntry:
|
|
184
|
+
"""Interface class which specifies generic log format for consumption
|
|
185
|
+
by other classes"""
|
|
186
|
+
year = ""
|
|
187
|
+
month = ""
|
|
188
|
+
day = ""
|
|
189
|
+
hour = ""
|
|
190
|
+
minute = ""
|
|
191
|
+
second = ""
|
|
192
|
+
host = ""
|
|
193
|
+
daemon = ""
|
|
194
|
+
log_entry = ""
|
|
195
|
+
|
|
196
|
+
def display(self):
|
|
197
|
+
print("Year: ", self.year, \
|
|
198
|
+
"Month:", self.month, \
|
|
199
|
+
"Day:", self.day, \
|
|
200
|
+
"Hour:", self.hour, \
|
|
201
|
+
"Minute:", self.minute, \
|
|
202
|
+
"Second:", self.second, \
|
|
203
|
+
"Host:", self.host, \
|
|
204
|
+
"Payload", self.log_entry)
|
|
205
|
+
|
|
206
|
+
def tally_logic(tally, tally_threshold, max_sample_lines):
|
|
207
|
+
if tally > tally_threshold:
|
|
208
|
+
return True
|
|
209
|
+
else:
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
# Declare Static Methods
|
|
213
|
+
tally_logic = staticmethod(tally_logic)
|
|
214
|
+
|
|
215
|
+
def set_abnormal(self, value):
|
|
216
|
+
self.year, \
|
|
217
|
+
self.month, \
|
|
218
|
+
self.day, \
|
|
219
|
+
self.hour, \
|
|
220
|
+
self.minute, \
|
|
221
|
+
self.second, \
|
|
222
|
+
self.host, \
|
|
223
|
+
self.daemon = ["1900", "01", "01", "01", "01", "01", "#", "#"]
|
|
224
|
+
self.log_entry = ' '.join(value)
|
|
225
|
+
|
|
226
|
+
def set_blank(self):
|
|
227
|
+
self.year, \
|
|
228
|
+
self.month, \
|
|
229
|
+
self.day, \
|
|
230
|
+
self.hour, \
|
|
231
|
+
self.minute, \
|
|
232
|
+
self.second, \
|
|
233
|
+
self.host, \
|
|
234
|
+
self.daemon = ["1900", "01", "01", "01", "01", "01", "#", "#"]
|
|
235
|
+
self.log_entry = "#"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class SyslogEntry(LogEntry):
|
|
239
|
+
"""Driver for Syslog. Conforms to LogEntry interface class."""
|
|
240
|
+
|
|
241
|
+
def __init__(self, line):
|
|
242
|
+
|
|
243
|
+
# Split the line up
|
|
244
|
+
value = line.split()
|
|
245
|
+
|
|
246
|
+
# Should be normal log entry
|
|
247
|
+
if len(value) >= 5:
|
|
248
|
+
|
|
249
|
+
# Syslog does not store year information so, set to current year
|
|
250
|
+
self.year = str(datetime.date.today().year)
|
|
251
|
+
self.month, self.day, clocktime, self.host, self.daemon = value[:5]
|
|
252
|
+
self.log_entry = ' '.join(value[5:])
|
|
253
|
+
self.hour, self.minute, self.second = clocktime.split(":")
|
|
254
|
+
|
|
255
|
+
# Convert month to integer
|
|
256
|
+
self.month = str(time.strptime(self.month, "%b")[1])
|
|
257
|
+
|
|
258
|
+
# Normalize integers to standard widths and convert to strings
|
|
259
|
+
self.year = str("%.4d" % (int(self.year)))
|
|
260
|
+
self.month = str("%.2d" % (int(self.month)))
|
|
261
|
+
self.day = str("%.2d" % (int(self.day)))
|
|
262
|
+
self.hour = str("%.2d" % (int(self.hour)))
|
|
263
|
+
self.minute = str("%.2d" % (int(self.minute)))
|
|
264
|
+
self.second = str("%.2d" % (int(self.second)))
|
|
265
|
+
|
|
266
|
+
# Abnormal log entry
|
|
267
|
+
elif len(value) >= 1:
|
|
268
|
+
self.year, \
|
|
269
|
+
self.month, \
|
|
270
|
+
self.day, \
|
|
271
|
+
self.hour, \
|
|
272
|
+
self.minute, \
|
|
273
|
+
self.second, \
|
|
274
|
+
self.host, \
|
|
275
|
+
self.daemon = ["1900", "01", "01", "01", "01", "01", "#", "#"]
|
|
276
|
+
self.log_entry = ' '.join(value)
|
|
277
|
+
|
|
278
|
+
# Blank line, will be sorted out by scrub
|
|
279
|
+
else:
|
|
280
|
+
self.year, \
|
|
281
|
+
self.month, \
|
|
282
|
+
self.day, \
|
|
283
|
+
self.hour, \
|
|
284
|
+
self.minute, \
|
|
285
|
+
self.second, \
|
|
286
|
+
self.host, \
|
|
287
|
+
self.daemon = ["1900", "01", "01", "01", "01", "01", "#", "#"]
|
|
288
|
+
self.log_entry = "#"
|
|
289
|
+
|
|
290
|
+
def is_type(line):
|
|
291
|
+
"""Standard function from interface class to determine type"""
|
|
292
|
+
|
|
293
|
+
global logging
|
|
294
|
+
|
|
295
|
+
if len(line) >= 6:
|
|
296
|
+
|
|
297
|
+
# Look for something similar to: "Feb 29 11:53:08" in first
|
|
298
|
+
# three columns
|
|
299
|
+
if re.search("[A-Z][a-z]{2}", line[0]) and \
|
|
300
|
+
re.search("[0-9][0-9]?", line[1]) and \
|
|
301
|
+
re.search("[0-9{2}:[0-9]{2}:[0-9]{2}", line[2]) and not \
|
|
302
|
+
(re.search("^pam_", line[5]) or \
|
|
303
|
+
re.search(r"^sshd\[", line[4])):
|
|
304
|
+
return True
|
|
305
|
+
else:
|
|
306
|
+
return False
|
|
307
|
+
else:
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
# Declare Static Methods
|
|
311
|
+
is_type = staticmethod(is_type)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class RSyslogEntry(LogEntry):
|
|
315
|
+
"""Driver for RSyslog. Conforms to LogEntry interface class."""
|
|
316
|
+
|
|
317
|
+
def __init__(self, line):
|
|
318
|
+
|
|
319
|
+
# Split the line up
|
|
320
|
+
value = line.split()
|
|
321
|
+
|
|
322
|
+
# Should be normal log entry
|
|
323
|
+
if len(value) >= 5:
|
|
324
|
+
|
|
325
|
+
# Complete major splits: 2010-06-24T17:56:32.197716-04:00
|
|
326
|
+
date, rtime = value[0].split("T") # Raw time
|
|
327
|
+
|
|
328
|
+
# High precision time with timezone info: 17:56:32.197716-04:00
|
|
329
|
+
hptime, offset = rtime.split("-")
|
|
330
|
+
|
|
331
|
+
# Patch for mixed enviornments, milliseconds do not get logged
|
|
332
|
+
# if older Ubuntu 8.04 boxes log to a newer 10.04 server with
|
|
333
|
+
# Rsyslog precision time on.
|
|
334
|
+
if re.search(r"[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{6}", hptime):
|
|
335
|
+
time, mseconds = hptime.split(".") # Miliseconds
|
|
336
|
+
else:
|
|
337
|
+
time = hptime
|
|
338
|
+
|
|
339
|
+
# Complete secondary splits
|
|
340
|
+
self.year, self.month, self.day = date.split("-")
|
|
341
|
+
self.hour, self.minute, self.second = time.split(":")
|
|
342
|
+
self.host = value[1]
|
|
343
|
+
self.daemon = value[2]
|
|
344
|
+
self.log_entry = ' '.join(value[3:])
|
|
345
|
+
|
|
346
|
+
# Normalize integers to standard widths and convert to strings
|
|
347
|
+
self.year = str("%.4d" % (int(self.year)))
|
|
348
|
+
self.month = str("%.2d" % (int(self.month)))
|
|
349
|
+
self.day = str("%.2d" % (int(self.day)))
|
|
350
|
+
self.hour = str("%.2d" % (int(self.hour)))
|
|
351
|
+
self.minute = str("%.2d" % (int(self.minute)))
|
|
352
|
+
self.second = str("%.2d" % (int(self.second)))
|
|
353
|
+
|
|
354
|
+
# Abnormal log entry
|
|
355
|
+
elif len(value) >= 1:
|
|
356
|
+
self.year, \
|
|
357
|
+
self.month, \
|
|
358
|
+
self.day, \
|
|
359
|
+
self.hour, \
|
|
360
|
+
self.minute, \
|
|
361
|
+
self.second, \
|
|
362
|
+
self.host, \
|
|
363
|
+
self.daemon = ["1900", "01", "01", "01", "01", "01", "#", "#"]
|
|
364
|
+
self.log_entry = ' '.join(value)
|
|
365
|
+
|
|
366
|
+
# Blank line, will be sorted out by scrub
|
|
367
|
+
else:
|
|
368
|
+
self.year, \
|
|
369
|
+
self.month, \
|
|
370
|
+
self.day, \
|
|
371
|
+
self.hour, \
|
|
372
|
+
self.minute, \
|
|
373
|
+
self.second, \
|
|
374
|
+
self.host, \
|
|
375
|
+
self.daemon = ["1900", "01", "01", "01", "01", "01", "#", "#"]
|
|
376
|
+
self.log_entry = "#"
|
|
377
|
+
|
|
378
|
+
def is_type(line):
|
|
379
|
+
"""Standard function from interface class to determine type"""
|
|
380
|
+
|
|
381
|
+
global logging
|
|
382
|
+
|
|
383
|
+
if len(line) >= 1:
|
|
384
|
+
|
|
385
|
+
# Look for something similar to: "2011-04-04T"
|
|
386
|
+
if re.search("[0-9]{4}-[0-9]{2}-[0-9]{2}T", line[0]):
|
|
387
|
+
return True
|
|
388
|
+
else:
|
|
389
|
+
return False
|
|
390
|
+
else:
|
|
391
|
+
return False
|
|
392
|
+
|
|
393
|
+
# Declare Static Methods
|
|
394
|
+
is_type = staticmethod(is_type)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class ApacheAccessEntry(LogEntry):
|
|
398
|
+
"""Driver for Apache Access formatted log files"""
|
|
399
|
+
|
|
400
|
+
def __init__(self, line):
|
|
401
|
+
|
|
402
|
+
# Split the line up
|
|
403
|
+
value = line.split()
|
|
404
|
+
|
|
405
|
+
# Should be normal log entry
|
|
406
|
+
if len(value) >= 12:
|
|
407
|
+
# Grab major chunks from the line
|
|
408
|
+
rhost, \
|
|
409
|
+
ident, \
|
|
410
|
+
ruser, \
|
|
411
|
+
apachedate, \
|
|
412
|
+
junk, \
|
|
413
|
+
junk2, \
|
|
414
|
+
uri, \
|
|
415
|
+
protocol, \
|
|
416
|
+
status, \
|
|
417
|
+
bytes, \
|
|
418
|
+
referer, \
|
|
419
|
+
agent = value[:12]
|
|
420
|
+
self.log_entry = uri
|
|
421
|
+
|
|
422
|
+
# Split up something that looks like this: [03/Aug/2009:11:53:08
|
|
423
|
+
datetime = apachedate.split(':')
|
|
424
|
+
date = datetime[0]
|
|
425
|
+
self.hour = datetime[1]
|
|
426
|
+
self.minute = datetime[2]
|
|
427
|
+
self.second = datetime[3]
|
|
428
|
+
dmy = date.split('/')
|
|
429
|
+
self.day = re.sub(r"\[", "", dmy[0])
|
|
430
|
+
self.month = dmy[1]
|
|
431
|
+
self.year = dmy[2]
|
|
432
|
+
self.host = uri
|
|
433
|
+
daemon = "webserver"
|
|
434
|
+
|
|
435
|
+
# Convert month to integer
|
|
436
|
+
self.month = time.strptime(self.month, "%b")[1]
|
|
437
|
+
|
|
438
|
+
# Normalize integers to standard widths and convert to strings
|
|
439
|
+
self.year = str("%.4d" % (int(self.year)))
|
|
440
|
+
self.month = str("%.2d" % (int(self.month)))
|
|
441
|
+
self.day = str("%.2d" % (int(self.day)))
|
|
442
|
+
self.hour = str("%.2d" % (int(self.hour)))
|
|
443
|
+
self.minute = str("%.2d" % (int(self.minute)))
|
|
444
|
+
self.second = str("%.2d" % (int(self.second)))
|
|
445
|
+
|
|
446
|
+
# Abnormal log entry
|
|
447
|
+
elif len(value) >= 1:
|
|
448
|
+
self.set_abnormal(value)
|
|
449
|
+
|
|
450
|
+
# Blank line, will be sorted out by scrub
|
|
451
|
+
else:
|
|
452
|
+
self.set_blank()
|
|
453
|
+
|
|
454
|
+
def is_type(line):
|
|
455
|
+
"""Standard function from interface class to determine type"""
|
|
456
|
+
|
|
457
|
+
global logging
|
|
458
|
+
|
|
459
|
+
if len(line) >= 4:
|
|
460
|
+
|
|
461
|
+
# Look for: "03/Aug/2009:11:53:08" in forth column
|
|
462
|
+
r = "[0-9]{2}/[a-zA-Z]{3}/[0-9]{4}:[0-9{2}:[0-9]{2}:[0-9]{2}"
|
|
463
|
+
if re.search(r, line[3]):
|
|
464
|
+
return True
|
|
465
|
+
else:
|
|
466
|
+
return False
|
|
467
|
+
else:
|
|
468
|
+
return False
|
|
469
|
+
|
|
470
|
+
# Declare Static Methods
|
|
471
|
+
is_type = staticmethod(is_type)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class ApacheErrorEntry(LogEntry):
|
|
475
|
+
"""Driver for Apache Error formatted log files"""
|
|
476
|
+
|
|
477
|
+
def __init__(self, line):
|
|
478
|
+
|
|
479
|
+
# Split the line up
|
|
480
|
+
value = line.split()
|
|
481
|
+
|
|
482
|
+
# Should be normal log entry
|
|
483
|
+
if len(value) >= 5:
|
|
484
|
+
# Grab major chunks from the line
|
|
485
|
+
# Split up something that looks like this:
|
|
486
|
+
# [Sat Feb 27 12:16:10 2010]
|
|
487
|
+
junk, self.month, self.day, clocktime, self.year = value[:5]
|
|
488
|
+
self.log_entry = ' '.join(value[5:])
|
|
489
|
+
self.hour, self.minute, self.second = clocktime.split(":")
|
|
490
|
+
|
|
491
|
+
# Convert month to integer
|
|
492
|
+
self.month = time.strptime(self.month, "%b")[1]
|
|
493
|
+
|
|
494
|
+
# Clean up the year field
|
|
495
|
+
self.year = re.sub(r"\]", "", self.year)
|
|
496
|
+
|
|
497
|
+
# Normalize integers to standard widths and convert to strings
|
|
498
|
+
self.year = str("%.4d" % (int(self.year)))
|
|
499
|
+
self.month = str("%.2d" % (int(self.month)))
|
|
500
|
+
self.day = str("%.2d" % (int(self.day)))
|
|
501
|
+
self.hour = str("%.2d" % (int(self.hour)))
|
|
502
|
+
self.minute = str("%.2d" % (int(self.minute)))
|
|
503
|
+
self.second = str("%.2d" % (int(self.second)))
|
|
504
|
+
|
|
505
|
+
# Abnormal log entry
|
|
506
|
+
elif len(value) >= 1:
|
|
507
|
+
self.set_abnormal(value)
|
|
508
|
+
|
|
509
|
+
# Blank line, will be sorted out by scrub
|
|
510
|
+
else:
|
|
511
|
+
self.set_blank()
|
|
512
|
+
|
|
513
|
+
def is_type(line):
|
|
514
|
+
"""Standard function from interface class to determine type"""
|
|
515
|
+
|
|
516
|
+
global logging
|
|
517
|
+
|
|
518
|
+
if len(line) >= 5:
|
|
519
|
+
|
|
520
|
+
# Look for : [Sat Feb 27 12:16:10 2010]
|
|
521
|
+
if re.search(r"[\[a-zA-Z]{3}", line[0]) and \
|
|
522
|
+
re.search("[0-9]{2}:[0-9]{2}:[0-9]{2}", line[3]) and \
|
|
523
|
+
re.search("[0-9]{4}", line[4]):
|
|
524
|
+
return True
|
|
525
|
+
else:
|
|
526
|
+
return False
|
|
527
|
+
else:
|
|
528
|
+
return False
|
|
529
|
+
|
|
530
|
+
# Declare Static Methods
|
|
531
|
+
is_type = staticmethod(is_type)
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
class SecureLogEntry(LogEntry):
|
|
535
|
+
"""Driver for Syslog. Conforms to LogEntry interface class."""
|
|
536
|
+
|
|
537
|
+
def __init__(self, line):
|
|
538
|
+
|
|
539
|
+
# Split the line up
|
|
540
|
+
value = line.split()
|
|
541
|
+
|
|
542
|
+
# Should be normal log entry
|
|
543
|
+
if len(value) >= 5:
|
|
544
|
+
# Syslog does not store year information so, set to current year
|
|
545
|
+
self.year = str(datetime.date.today().year)
|
|
546
|
+
self.month, self.day, clocktime, self.host, self.daemon = value[:5]
|
|
547
|
+
self.log_entry = ' '.join(value[5:])
|
|
548
|
+
self.hour, self.minute, self.second = clocktime.split(":")
|
|
549
|
+
|
|
550
|
+
# Convert month to integer
|
|
551
|
+
self.month = str(time.strptime(self.month, "%b")[1])
|
|
552
|
+
|
|
553
|
+
# Normalize integers to standard widths
|
|
554
|
+
self.year = str("%.4d" % (int(self.year)))
|
|
555
|
+
self.month = str("%.2d" % (int(self.month)))
|
|
556
|
+
self.day = str("%.2d" % (int(self.day)))
|
|
557
|
+
self.hour = str("%.2d" % (int(self.hour)))
|
|
558
|
+
self.minute = str("%.2d" % (int(self.minute)))
|
|
559
|
+
self.second = str("%.2d" % (int(self.second)))
|
|
560
|
+
|
|
561
|
+
# Abnormal log entry
|
|
562
|
+
elif len(value) >= 1:
|
|
563
|
+
self.set_abnormal(value)
|
|
564
|
+
|
|
565
|
+
# Blank line, will be sorted out by scrub
|
|
566
|
+
else:
|
|
567
|
+
self.set_blank()
|
|
568
|
+
|
|
569
|
+
def is_type(line):
|
|
570
|
+
"""Standard function from interface class to determine type"""
|
|
571
|
+
|
|
572
|
+
global logging
|
|
573
|
+
|
|
574
|
+
if len(line) >= 6:
|
|
575
|
+
|
|
576
|
+
# Look for something similar to: "29 11:53:08" in third column
|
|
577
|
+
if re.search("[0-9][0-9]?", line[1]) \
|
|
578
|
+
and re.search("[0-9{2}:[0-9]{2}:[0-9]{2}", line[2]) \
|
|
579
|
+
and (re.search("^pam_", line[5]) \
|
|
580
|
+
or re.search(r"^sshd\[", line[4])):
|
|
581
|
+
return True
|
|
582
|
+
else:
|
|
583
|
+
return False
|
|
584
|
+
else:
|
|
585
|
+
return False
|
|
586
|
+
|
|
587
|
+
# Declare Static Methods
|
|
588
|
+
is_type = staticmethod(is_type)
|
|
589
|
+
|
|
590
|
+
def tally_logic(tally, tally_threshold, max_sample_lines):
|
|
591
|
+
"""Override tally logic for secure logs"""
|
|
592
|
+
|
|
593
|
+
if tally >= max_sample_lines:
|
|
594
|
+
return True
|
|
595
|
+
else:
|
|
596
|
+
return False
|
|
597
|
+
|
|
598
|
+
# Declare Static Methods
|
|
599
|
+
tally_logic = staticmethod(tally_logic)
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
class ScriptlogEntry(LogEntry):
|
|
603
|
+
"""
|
|
604
|
+
Driver for scriptlog entries. Conforms to LogEntry interface class.
|
|
605
|
+
This allows for a standard syslog entry to have extra fields which
|
|
606
|
+
are used with scriptlogs.
|
|
607
|
+
"""
|
|
608
|
+
|
|
609
|
+
# Extra variables
|
|
610
|
+
label = "__none__"
|
|
611
|
+
id = "__none__"
|
|
612
|
+
type = "__none__"
|
|
613
|
+
|
|
614
|
+
def __init__(self, line):
|
|
615
|
+
|
|
616
|
+
# Split the line up
|
|
617
|
+
value = line.split()
|
|
618
|
+
|
|
619
|
+
# Should be normal log entry
|
|
620
|
+
if len(value) >= 5:
|
|
621
|
+
|
|
622
|
+
# Syslog does not store year information so scriptlog does not
|
|
623
|
+
# So set to current year, set the other fields normally
|
|
624
|
+
self.year = datetime.date.today().year
|
|
625
|
+
self.month, \
|
|
626
|
+
self.day, \
|
|
627
|
+
time, \
|
|
628
|
+
self.host, \
|
|
629
|
+
self.daemon, \
|
|
630
|
+
self.label, \
|
|
631
|
+
self.id, \
|
|
632
|
+
self.type = value[:8]
|
|
633
|
+
|
|
634
|
+
self.log_entry = ' '.join(value[8:])
|
|
635
|
+
self.hour, self.minute, self.second = time.split(":")
|
|
636
|
+
|
|
637
|
+
# Normalize integers to standard widths and convert to strings
|
|
638
|
+
self.year = str("%.4d" % (int(self.year)))
|
|
639
|
+
self.month = str("%.2d" % (int(self.month)))
|
|
640
|
+
self.day = str("%.2d" % (int(self.day)))
|
|
641
|
+
self.hour = str("%.2d" % (int(self.hour)))
|
|
642
|
+
self.minute = str("%.2d" % (int(self.minute)))
|
|
643
|
+
self.second = str("%.2d" % (int(self.second)))
|
|
644
|
+
|
|
645
|
+
# Abnormal log entry
|
|
646
|
+
elif len(value) >= 1:
|
|
647
|
+
self.set_abnormal(value)
|
|
648
|
+
|
|
649
|
+
# Blank line, will be sorted out by scrub
|
|
650
|
+
else:
|
|
651
|
+
self.set_blank()
|
|
652
|
+
|
|
653
|
+
def is_type(line, label="__none__"):
|
|
654
|
+
"""Standard function from interface class to determine type"""
|
|
655
|
+
|
|
656
|
+
# Split the line up
|
|
657
|
+
value = str(line).split()
|
|
658
|
+
|
|
659
|
+
if len(value) >= 8:
|
|
660
|
+
|
|
661
|
+
# Look for special label to determine scriptlog type
|
|
662
|
+
if re.search(re.escape(value[5]), re.escape(label)):
|
|
663
|
+
return True
|
|
664
|
+
else:
|
|
665
|
+
return False
|
|
666
|
+
else:
|
|
667
|
+
return False
|
|
668
|
+
|
|
669
|
+
# Declare Static Methods
|
|
670
|
+
is_type = staticmethod(is_type)
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
class RawEntry(LogEntry):
|
|
674
|
+
"""
|
|
675
|
+
Driver for Raw log files. Conforms to LogEntry interface class.
|
|
676
|
+
This also allows raw logs to contain all of the correct fields
|
|
677
|
+
to be worked with just like other entries that actually have time
|
|
678
|
+
values
|
|
679
|
+
"""
|
|
680
|
+
|
|
681
|
+
def __init__(self, line):
|
|
682
|
+
|
|
683
|
+
# Split the line up
|
|
684
|
+
value = line.split()
|
|
685
|
+
|
|
686
|
+
# Fake the time/date values, put the entire line in the key
|
|
687
|
+
if len(value) >= 1:
|
|
688
|
+
self.set_abnormal(value)
|
|
689
|
+
|
|
690
|
+
# Blank line, will be sorted out by scrub
|
|
691
|
+
else:
|
|
692
|
+
self.set_blank()
|
|
693
|
+
|
|
694
|
+
def is_type(line):
|
|
695
|
+
"""
|
|
696
|
+
Do minimum checking to ensure there is some data
|
|
697
|
+
"""
|
|
698
|
+
|
|
699
|
+
if len(line) >= 1:
|
|
700
|
+
|
|
701
|
+
# Look for any length of text in the line
|
|
702
|
+
if re.search(".+", str(line)):
|
|
703
|
+
return True
|
|
704
|
+
else:
|
|
705
|
+
return False
|
|
706
|
+
else:
|
|
707
|
+
return False
|
|
708
|
+
|
|
709
|
+
# Declare Static Methods
|
|
710
|
+
is_type = staticmethod(is_type)
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
class SnortEntry(LogEntry):
|
|
714
|
+
"""
|
|
715
|
+
Driver for Snort formatted log files. Conforms to LogEntry interface class.
|
|
716
|
+
"""
|
|
717
|
+
|
|
718
|
+
def __init__(self, line):
|
|
719
|
+
|
|
720
|
+
# Split the line up
|
|
721
|
+
value = line.split()
|
|
722
|
+
|
|
723
|
+
# Should be normal log entry
|
|
724
|
+
if len(value) >= 2:
|
|
725
|
+
|
|
726
|
+
# Snort does not store year information so, set to current year
|
|
727
|
+
self.year = datetime.date.today().year
|
|
728
|
+
|
|
729
|
+
# Initial break down
|
|
730
|
+
snortdate = value[:1]
|
|
731
|
+
self.log_entry = ' '.join(value[1:])
|
|
732
|
+
|
|
733
|
+
# Looks like "09/29-10:18:46.026172"
|
|
734
|
+
snortdate, junk = snortdate[0].split('.')
|
|
735
|
+
|
|
736
|
+
# Looks like "09/29-10:18:46"
|
|
737
|
+
self.month, snortdate = snortdate.split('/')
|
|
738
|
+
|
|
739
|
+
# Looks like "29-10:18:46"
|
|
740
|
+
self.day, snortdate = snortdate.split('-')
|
|
741
|
+
|
|
742
|
+
# Looks like "10:18:46"
|
|
743
|
+
self.hour, self.minute, self.second = snortdate.split(':')
|
|
744
|
+
|
|
745
|
+
# Normalize integers to standard widths and convert to strings
|
|
746
|
+
self.year = str("%.4d" % (int(self.year)))
|
|
747
|
+
self.month = str("%.2d" % (int(self.month)))
|
|
748
|
+
self.day = str("%.2d" % (int(self.day)))
|
|
749
|
+
self.hour = str("%.2d" % (int(self.hour)))
|
|
750
|
+
self.minute = str("%.2d" % (int(self.minute)))
|
|
751
|
+
self.second = str("%.2d" % (int(self.second)))
|
|
752
|
+
|
|
753
|
+
# Abnormal value
|
|
754
|
+
elif len(value) >= 1:
|
|
755
|
+
self.set_abnormal(value)
|
|
756
|
+
|
|
757
|
+
# Blank line, will be sorted out by scrub
|
|
758
|
+
else:
|
|
759
|
+
self.set_blank()
|
|
760
|
+
|
|
761
|
+
def is_type(line):
|
|
762
|
+
|
|
763
|
+
global logging
|
|
764
|
+
|
|
765
|
+
if len(line) >= 4:
|
|
766
|
+
|
|
767
|
+
# Look for : "09/29-10:18:46.026172" in first column
|
|
768
|
+
r = r"[0-9]{2}\/[0-9]{2}\-[0-9]{2}\:[0-9]{2}\:[0-9]{2}\.[0-9]{6}"
|
|
769
|
+
if re.search(r, line[0]):
|
|
770
|
+
return True
|
|
771
|
+
else:
|
|
772
|
+
return False
|
|
773
|
+
else:
|
|
774
|
+
return False
|
|
775
|
+
|
|
776
|
+
# Declare Static Methods
|
|
777
|
+
is_type = staticmethod(is_type)
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
# Automatically load a list of drivers for each file type this will be used
|
|
781
|
+
# to determine what kind of log it is. Do NOT append the parent LogEntry and
|
|
782
|
+
# append RawEntry to the end to preserve last resort logic
|
|
783
|
+
ma = sys.modules[__name__].__dict__ # module attributes
|
|
784
|
+
entry_types = list()
|
|
785
|
+
|
|
786
|
+
for i in list(ma.keys()):
|
|
787
|
+
if isinstance(ma[i], type):
|
|
788
|
+
if issubclass(ma[i], ma['LogEntry']) and \
|
|
789
|
+
ma[i].__name__ != "LogEntry" and \
|
|
790
|
+
ma[i].__name__ != "RawEntry":
|
|
791
|
+
entry_types.append(ma[i])
|
|
792
|
+
|
|
793
|
+
entry_types.append(RawEntry)
|