#! /usr/bin/python -E """Analyzes audit output. Some more documentation should go here. Package: @PACKAGE@ Version: @VERSION@ """ # Authors: John D. Ramsdell # # Copyright (C) 2007 The MITRE Corporation # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; version 2 only # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys, getopt, auparse, os.path, copy, string # Log Parsing class AuditLog(object): """AuditLog(AuParser or file, bool) Encapsulates a log accessed by the auparse module. It provides an iterator to the log's events. Each call to the next method of the iterator returns an AuditEvent. The boolean determines if numeric entities in fields are interpreted. """ def __init__(self, au, interpret = False): if isinstance(au, file): self.au = auparse.AuParser(auparse.AUSOURCE_FILE_POINTER, au) else: self.au = au self.interpret = interpret def __iter__(self): return AuditLogIter(self) class AuditLogIter(object): """AuditLogIter(AuditLog) An iterator for an audit log. """ def __init__(self, aulog): self.au = aulog.au self.interpret = aulog.interpret def next(self): """Returns an AuditEvent """ au = self.au interpret = self.interpret if not au.parse_next_event(): raise StopIteration() event = AuditEvent(au.get_timestamp()) for i in range(au.get_num_records()): record = AuditRecord(event) for j in range(au.get_num_fields()): name = au.get_field_name() if interpret: value = au.interpret_field() else: value = au.get_field_str() record[name] = value au.next_field() event.append(record) au.next_record() return event class AuditEvent(list): """AuditEvent(AuEvent) An audit event is represented as a list of AuditRecord's. Each AuditRecord is a dictionary that represents one of the records that make up the event. """ def __init__(self, timestamp): self.timestamp = timestamp def get_timestamp(self): """Get the timestamp associated with this event.""" return self.timestamp def find_record_of_type(self, typ): """Find record of type Returns a record in the event that has the given type. Returns None when there is no such record. """ for record in self: try: if record["type"] == typ: return record except KeyError: pass return None def find_value(self, name): """Find a value Returns a value associated with the given name in some record in the event. Raises KeyError if no field has the given name. """ for record in self: try: return record[name] except KeyError: pass raise KeyError def find_path_record(self, item): """Find a PATH record for the given item. Returns the PATH record for the given item. Return None if PATH record cannot be found. """ item = str(item) # Ensure item is a string for record in self: if record.is_path_record(item): return record return None class AuditRecord(dict): """AuditRecord(AuditEvent) An audit record is a dictionary. """ def __init__(self, event): self.event = event def get_event(self): """Get the event associated with this record.""" return self.event def is_path_record(self, item): """Is this a PATH record for the given item?""" try: return self["type"] == "PATH" and self["item"] == item except KeyError: return False def args2aulog(): """Make an AuditLog from command line arguments Create an audit log object based on the command line arguments passsed to this program. Returns an AuditLog. """ interpret = False # Event reading mode output_file = None # Output file name for results try: long_opts = ["help", "interpret", "raw", "output="] opts, args = getopt.getopt(sys.argv[1:], "hiro:", long_opts) except getopt.GetoptError: # Bad options usage() sys.exit(2) for o, a in opts: if o in ("-h", "--help"): # Print help information usage() sys.exit(0) elif o in ("-i", "--interpret"): interpret = True elif o in ("-r", "--raw"): interpret = False elif o in ("-o", "--output"): # Record output file name output_file = a # Determine input source if len(args) > 0: au = auparse.AuParser(auparse.AUSOURCE_FILE_ARRAY, args) sys.stdin.close() else: au = sys.stdin # Open output file as standard output if output_file: sys.stdout.close() try: sys.stdout = file(output_file, 'w') except IOError: sys.stderr.write("cannot open %s for writing" % output_file) sys.exit(1) return AuditLog(au, interpret) def usage(): """Show usage Print a ussage message that includes version information. """ help = """Version: %s %s Usage: %s [OPTIONS] [FILE...] -o FILE, --output=FILE output to FILE (default is standard output) -i, --interpret interpret numeric entities into text -r --raw do not interpret numeric entities (default) -h, --help print this message and exit With no FILEs, read from the standard input. """ sys.stderr.write(help % ("@PACKAGE@", "@VERSION@", sys.argv[0])) # Consume a log def run(log): """The main loop for consuming a log.""" openno = 0 cwdno = 0 pathno = 0 cwdnopathno = 0 for event in log: record = event.find_record_of_type("SYSCALL") if record: syscall = record["syscall"] if syscall == "open": openno = openno + 1 has_cwd = event.find_record_of_type("CWD") has_path = event.find_record_of_type("PATH") if has_cwd: cwdno = cwdno + 1 if has_path: pathno = pathno + 1 if has_path and not has_cwd: cwdnopathno = cwdnopathno + 1 print "Of %d events with a SYSCALL record with syscall=open" % openno print "%d have CWD" % cwdno print "%d have PATH" % pathno print "%d have CWD but no PATH" % cwdnopathno def main(): """Main routine Perform command line processing and then conume the log. """ run(args2aulog()) if __name__ == "__main__": main()