#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright 2014 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 specifiSc language governing permissions and # limitations under the License. """Parser for PL-SQL Developer Recall files.""" import construct import os from plaso.lib import errors from plaso.lib import event from plaso.lib import timelib from plaso.lib import utils from plaso.parsers import interface from plaso.parsers import manager class PlsRecallEvent(event.EventObject): """Convenience class for a PL-SQL Recall file container.""" DATA_TYPE = 'PLSRecall:event' def __init__(self, timestamp, sequence, user, database, query): """Initializes the event object. Args: timestamp: The timestamp when the entry was created. sequence: Sequence indicates the order of execution. username: The username that made the query. database_name: String containing the databe name. query: String containing the PL-SQL query. """ super(PlsRecallEvent, self).__init__() self.timestamp = timestamp self.sequence = sequence self.username = user self.database_name = database self.query = query class PlsRecallParser(interface.BaseParser): """Parse PL-SQL Recall files. Parser is based on a: TRecallRecord = packed record Sequence: Integer; TimeStamp: TDateTime; Username: array[0..30] of Char; Database: array[0..80] of Char; Text: array[0..4000] of Char; end; Delphi TDateTime is a little endian 64-bit floating point without any time zone information """ NAME = 'pls_recall' DESCRIPTION = u'Parser for PL-SQL Recall files.' PLS_STRUCT = construct.Struct( 'PL-SQL_Recall', construct.ULInt32('Sequence'), construct.LFloat64('TimeStamp'), construct.String('Username', 31, None, '\x00'), construct.String('Database', 81, None, '\x00'), construct.String('Query', 4001, None, '\x00')) def Parse(self, parser_context, file_entry, parser_chain=None): """Extract entries from a PLSRecall.dat file. Args: parser_context: A parser context object (instance of ParserContext). file_entry: A file entry object (instance of dfvfs.FileEntry). parser_chain: Optional string containing the parsing chain up to this point. The default is None. """ file_object = file_entry.GetFileObject() try: is_pls = self.VerifyFile(file_object) except (IOError, construct.FieldError) as exception: file_object.close() raise errors.UnableToParseFile(( u'Not a PLSrecall File, unable to parse.' u'with error: {0:s}').format(exception)) if not is_pls: file_object.close() raise errors.UnableToParseFile( u'Not a PLSRecall File, unable to parse.') file_object.seek(0, os.SEEK_SET) pls_record = self.PLS_STRUCT.parse_stream(file_object) # Add ourselves to the parser chain, which will be used in all subsequent # event creation in this parser. parser_chain = self._BuildParserChain(parser_chain) while pls_record: event_object = PlsRecallEvent( timelib.Timestamp.FromDelphiTime(pls_record.TimeStamp), pls_record.Sequence, pls_record.Username, pls_record.Database, pls_record.Query) parser_context.ProduceEvent( event_object, parser_chain=parser_chain, file_entry=file_entry) try: pls_record = self.PLS_STRUCT.parse_stream(file_object) except construct.FieldError as exception: # The code has reached the end of file (EOF). break file_object.close() def VerifyFile(self, file_object): """Check if the file is a PLSRecall.dat file. Args: file_object: file that we want to check. Returns: True if this is a valid PLSRecall.dat file, otherwise False. """ file_object.seek(0, os.SEEK_SET) # The file consists of PL-SQL structures that are equal # size (4125 bytes) TRecallRecord records. It should be # noted that the query value is free form. try: structure = self.PLS_STRUCT.parse_stream(file_object) except (IOError, construct.FieldError): return False # Verify few entries inside the structure. try: timestamp = timelib.Timestamp.FromDelphiTime(structure.TimeStamp) except ValueError: return False if timestamp <= 0: return False # TODO: Add other verification checks here. For instance make sure # that the query actually looks like a SQL query. This structure produces a # lot of false positives and thus we need to add additional verification to # make sure we are not parsing non-PLSRecall files. # Another check might be to make sure the username looks legitimate, or the # sequence number, or the database name. # For now we just check if all three fields pass our "is this a text" test. if not utils.IsText(structure.Username): return False if not utils.IsText(structure.Query): return False if not utils.IsText(structure.Database): return False return True manager.ParsersManager.RegisterParser(PlsRecallParser)