terom@21: import re terom@21: terom@48: class BaseFilter (object) : terom@48: """ terom@48: A filter object matches incoming lines, to determine how they are handled, classify them, and optionally reformat them terom@48: """ terom@48: terom@48: # the LogWatchModule event to send terom@57: label = None terom@48: terom@57: def __init__ (self, label) : terom@57: self.label = label terom@21: terom@21: def test (self, line) : terom@48: """ terom@53: Match against the given line. See match() for return codes terom@53: """ terom@21: terom@53: raise NotImplementedError() terom@53: terom@53: def match (self, msg) : terom@53: """ terom@53: Match against the given SyslogMessage, and return one of: terom@48: None - filter did not match, continue terom@48: False - filter matched, line should be dropped terom@57: (label, ) terom@57: - filter matched, pass formatted output with given label terom@48: """ terom@53: terom@53: # default to a full-line match terom@53: return self.test(str(msg)) terom@48: terom@48: class FullFilter (BaseFilter) : terom@48: """ terom@48: A trivial filter that matches every possible line as-is terom@48: """ terom@48: terom@48: def test (self, line) : terom@48: # pass through terom@57: return self.label, line terom@48: terom@48: class NullFilter (BaseFilter) : terom@48: """ terom@48: A filter that drops every line matching a given regexp terom@48: """ terom@48: terom@53: def __init__ (self, pattern, flags=0) : terom@57: # don't need an label terom@48: terom@21: self.regexp = re.compile(pattern, flags) terom@21: terom@21: def test (self, line) : terom@21: match = self.regexp.search(line) terom@21: terom@21: if match : terom@48: # drop terom@21: return False terom@21: terom@48: class SimpleFilter (BaseFilter) : terom@48: """ terom@48: A simple filter that passes through any lines that match, optionally reformatting them with the given string terom@48: pattern, using the regexp match groups as parameters. terom@48: """ terom@21: terom@57: def __init__ (self, label, pattern, format=None) : terom@57: super(SimpleFilter, self).__init__(label) terom@48: terom@48: # store terom@21: self.regexp = re.compile(pattern) terom@21: self.format = format terom@21: terom@21: def test (self, line) : terom@48: # match terom@21: match = self.regexp.search(line) terom@21: terom@48: if not match : terom@48: # continue terom@48: return None terom@21: terom@48: # reformat? terom@48: if self.format : terom@48: # format with regexp match groups terom@57: return self.label, self.format % match.groupdict() terom@48: terom@48: else : terom@48: # match as-is terom@57: return self.label, line terom@21: terom@53: class SyslogFilter (BaseFilter) : terom@53: """ terom@53: A more advanced filter that can match against fields in the syslog message. terom@53: """ terom@53: terom@53: def __init__ (self, label, pattern=None, program=None, drop=False, format=None, re_flags=0) : terom@53: """ terom@53: Filter using the given criteria: terom@53: terom@53: label - label match output with given label terom@53: terom@53: pattern - match message content against given regexp terom@53: program - (optional) case-insensitive match against message tag's program component terom@53: May also be False to indicate no tag terom@53: terom@53: drop - drop this message if this matches terom@53: format - (optional) format output with given format string terom@53: terom@53: re_flags - (optional) flags for regular expression terom@53: """ terom@53: terom@53: # XXX: super(SyslogFilter, self).__init__(label) terom@53: self.label = label terom@53: terom@53: # store terom@55: if pattern : terom@55: self.regexp = re.compile(pattern, re_flags) terom@55: else : terom@55: self.regexp = None terom@55: terom@53: self.program = program terom@53: terom@53: self.drop = drop terom@53: self.format = format terom@53: terom@53: def match (self, msg) : terom@53: """ terom@53: Evaluate match on given message terom@53: """ terom@53: terom@53: # use the SyslogMessage's match method terom@53: match = msg.match(self.regexp, self.program) terom@53: terom@53: # handle result terom@53: if match is False : terom@53: # nack terom@53: return None terom@53: terom@53: elif self.drop : terom@53: # halt processing terom@53: return False terom@53: terom@53: elif self.format : terom@53: # the messages properties terom@53: params = msg.properties() terom@53: terom@53: # the regexp'd matched params terom@53: params.update(match) terom@53: terom@53: # formatted output terom@53: return self.label, self.format % params terom@53: terom@53: else : terom@53: # boring output terom@53: return self.label, str(msg) terom@53: terom@53: terom@48: # matches a timestamp prefix terom@21: _timestamp = "\w{3} [0-9 ]\d \d{2}:\d{2}:\d{2}" terom@21: terom@48: terom@55: # match all lines, but doesn't include the timestamp terom@55: all = SyslogFilter('all', terom@55: format = "%(hostname)s %(message)s" terom@21: ) terom@21: terom@48: # match sudo invocations, reformatting them nicely terom@53: sudo = SyslogFilter('sudo', terom@53: program = "sudo", terom@56: pattern = r"^\s*(?P\S+) : TTY=(?P\S+) ; PWD=(?P.+?) ; USER=(?P\S+) ; COMMAND=(?P.*)", terom@53: format = "%(username)s:%(tty)s - %(target_user)s@%(hostname)s:%(pwd)s - %(command)r", terom@21: ) terom@21: terom@48: # match accepted ssh logins terom@55: ssh = SyslogFilter('ssh', terom@55: program = "sshd", terom@56: pattern = r"^\s*Accepted password for (?P\S+) from (?P\S+) port (?P\S+) (?P\S+)", terom@55: format = "SSH login for %(username)s@%(hostname)s from %(ip)s:%(port)s", terom@21: ) terom@21: terom@55: # drops all output from cron terom@56: # XXX: what about the same from su? terom@55: cron_killer = SyslogFilter('all', terom@55: program = "cron", terom@55: drop = True, terom@21: ) terom@21: terom@48: # drops `su nobody` output (from cron) terom@56: su_nobody_killer = SyslogFilter('all', terom@56: program = "su", terom@56: pattern = r"^(Successful su for nobody by root|\+ \?\?\? root:nobody)$", terom@56: re_flags = re.IGNORECASE, terom@56: drop = True terom@21: ) terom@48: