#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright 2013 The Plaso Project Authors. # Please see the AUTHORS file for details on individual authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Contains a formatter for a dynamic output module for plaso.""" import logging import re from plaso.formatters import manager as formatters_manager from plaso.lib import errors from plaso.lib import output from plaso.lib import timelib from plaso.output import helper class Dynamic(output.FileLogOutputFormatter): """Dynamic selection of fields for a separated value output format.""" FORMAT_ATTRIBUTE_RE = re.compile('{([^}]+)}') # A dict containing mappings between "special" attributes and # how they should be calculated and presented. # They should be documented here: # http://plaso.kiddaland.net/usage/psort/output SPECIAL_HANDLING = { 'date': 'ParseDate', 'datetime': 'ParseDateTime', 'description': 'ParseMessage', 'description_short': 'ParseMessageShort', 'host': 'ParseHostname', 'hostname': 'ParseHostname', 'inode': 'ParseInode', 'macb': 'ParseMacb', 'message': 'ParseMessage', 'message_short': 'ParseMessageShort', 'source': 'ParseSourceShort', 'sourcetype': 'ParseSource', 'source_long': 'ParseSource', 'tag': 'ParseTag', 'time': 'ParseTime', 'timezone': 'ParseZone', 'type': 'ParseTimestampDescription', 'user': 'ParseUsername', 'username': 'ParseUsername', 'zone': 'ParseZone', } def ParseTimestampDescription(self, event_object): """Return the timestamp description.""" return getattr(event_object, 'timestamp_desc', '-') def ParseTag(self, event_object): """Return tagging information.""" tag = getattr(event_object, 'tag', None) if not tag: return u'-' return u' '.join(tag.tags) def ParseSource(self, event_object): """Return the source string.""" # TODO: move this to an output module interface. event_formatter = formatters_manager.EventFormatterManager.GetFormatter( event_object) if not event_formatter: raise errors.NoFormatterFound( u'Unable to find no event formatter for: {0:s}.'.format( event_object.DATA_TYPE)) _, source = event_formatter.GetSources(event_object) return source def ParseSourceShort(self, event_object): """Return the source string.""" # TODO: move this to an output module interface. event_formatter = formatters_manager.EventFormatterManager.GetFormatter( event_object) if not event_formatter: raise errors.NoFormatterFound( u'Unable to find no event formatter for: {0:s}.'.format( event_object.DATA_TYPE)) source, _ = event_formatter.GetSources(event_object) return source def ParseZone(self, _): """Return a timezone.""" return self.zone def ParseDate(self, event_object): """Return a date string from a timestamp value.""" try: date_use = timelib.Timestamp.CopyToDatetime( event_object.timestamp, self.zone, raise_error=True) except OverflowError as exception: logging.error(( u'Unable to copy {0:d} into a human readable timestamp with error: ' u'{1:s}. Event {2:d}:{3:d} triggered the exception.').format( event_object.timestamp, exception, getattr(event_object, 'store_number', u''), getattr(event_object, 'store_index', u''))) return u'0000-00-00' return u'{0:04d}-{1:02d}-{2:02d}'.format( date_use.year, date_use.month, date_use.day) def ParseDateTime(self, event_object): """Return a datetime object from a timestamp, in an ISO format.""" try: return timelib.Timestamp.CopyToIsoFormat( event_object.timestamp, timezone=self.zone, raise_error=True) except OverflowError as exception: logging.error(( u'Unable to copy {0:d} into a human readable timestamp with error: ' u'{1:s}. Event {2:d}:{3:d} triggered the exception.').format( event_object.timestamp, exception, getattr(event_object, 'store_number', u''), getattr(event_object, 'store_index', u''))) return u'0000-00-00T00:00:00' def ParseTime(self, event_object): """Return a timestamp string from an integer timestamp value.""" try: date_use = timelib.Timestamp.CopyToDatetime( event_object.timestamp, self.zone, raise_error=True) except OverflowError as exception: logging.error(( u'Unable to copy {0:d} into a human readable timestamp with error: ' u'{1:s}. Event {2:d}:{3:d} triggered the exception.').format( event_object.timestamp, exception, getattr(event_object, 'store_number', u''), getattr(event_object, 'store_index', u''))) return u'00:00:00' return u'{0:02d}:{1:02d}:{2:02d}'.format( date_use.hour, date_use.minute, date_use.second) def ParseHostname(self, event_object): """Return a hostname.""" hostname = getattr(event_object, 'hostname', '') if self.store: if not hostname: hostname = self._hostnames.get(event_object.store_number, '-') return hostname # TODO: move this into a base output class. def ParseUsername(self, event_object): """Determines an username based on an event and extracted information. Uses the extracted information from the pre processing information and the event object itself to determine an username. Args: event_object: The event object (instance of EventObject). Returns: An Unicode string containing the username, or - if none found. """ username = getattr(event_object, u'username', u'-') if self.store: pre_obj = self._preprocesses.get(event_object.store_number) if pre_obj: check_user = pre_obj.GetUsernameById(username) if check_user != u'-': username = check_user if username == '-' and hasattr(event_object, u'user_sid'): if not pre_obj: return getattr(event_object, u'user_sid', u'-') return pre_obj.GetUsernameById( getattr(event_object, u'user_sid', u'-')) return username def ParseMessage(self, event_object): """Return the message string from the EventObject. Args: event_object: The event object (EventObject). Raises: errors.NoFormatterFound: If no formatter for that event is found. """ # TODO: move this to an output module interface. event_formatter = formatters_manager.EventFormatterManager.GetFormatter( event_object) if not event_formatter: raise errors.NoFormatterFound( u'Unable to find no event formatter for: {0:s}.'.format( event_object.DATA_TYPE)) msg, _ = event_formatter.GetMessages(event_object) return msg def ParseMessageShort(self, event_object): """Return the message string from the EventObject. Args: event_object: The event object (EventObject). Raises: errors.NoFormatterFound: If no formatter for that event is found. """ # TODO: move this to an output module interface. event_formatter = formatters_manager.EventFormatterManager.GetFormatter( event_object) if not event_formatter: raise errors.NoFormatterFound( u'Unable to find no event formatter for: {0:s}.'.format( event_object.DATA_TYPE)) _, msg_short = event_formatter.GetMessages(event_object) return msg_short def ParseInode(self, event_object): """Return an inode number.""" inode = getattr(event_object, 'inode', '-') if inode == '-': if hasattr(event_object, 'pathspec') and hasattr( event_object.pathspec, 'image_inode'): inode = event_object.pathspec.image_inode return inode def ParseMacb(self, event_object): """Return a legacy MACB representation.""" return helper.GetLegacy(event_object) def Start(self): """Returns a header for the output.""" # Start by finding out which fields are to be used. self.fields = [] if self._filter: self.fields = self._filter.fields self.separator = self._filter.separator else: self.separator = u',' if not self.fields: # TODO: Evaluate which fields should be included by default. self.fields = [ 'datetime', 'timestamp_desc', 'source', 'source_long', 'message', 'parser', 'display_name', 'tag', 'store_number', 'store_index'] if self.store: self._hostnames = helper.BuildHostDict(self.store) self._preprocesses = {} for info in self.store.GetStorageInformation(): if hasattr(info, 'store_range'): for store_number in range(info.store_range[0], info.store_range[1]): self._preprocesses[store_number] = info self.filehandle.WriteLine('{0:s}\n'.format( self.separator.join(self.fields))) def WriteEvent(self, event_object): """Write a single event.""" try: self.EventBody(event_object) except errors.NoFormatterFound: logging.error(u'Unable to output line, no formatter found.') logging.error(event_object) def EventBody(self, event_object): """Formats data as "dynamic" CSV and writes to the filehandle.""" row = [] for field in self.fields: has_call_back = self.SPECIAL_HANDLING.get(field, None) call_back = None if has_call_back: call_back = getattr(self, has_call_back, None) if call_back: row.append(call_back(event_object)) else: row.append(getattr(event_object, field, u'-')) out_write = u'{0:s}\n'.format( self.separator.join(unicode(x).replace( self.separator, u' ') for x in row)) self.filehandle.WriteLine(out_write)