#!/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. """This file contains a parser for the Mac OS X MacKeeper cache database.""" import json from plaso.lib import event from plaso.lib import eventdata from plaso.lib import timelib from plaso.parsers import sqlite from plaso.parsers.sqlite_plugins import interface def DictToList(data_dict): """Take a dict object and return a list of strings back.""" ret_list = [] for key, value in data_dict.iteritems(): if key in ('body', 'datetime', 'type', 'room', 'rooms', 'id'): continue ret_list.append(u'{0:s} = {1!s}'.format(key, value)) return ret_list def ExtractJQuery(jquery_raw): """Extract and return the data inside a JQuery as a dict object.""" data_part = u'' if not jquery_raw: return {} if '[' in jquery_raw: _, _, first_part = jquery_raw.partition('[') data_part, _, _ = first_part.partition(']') elif jquery_raw.startswith('//'): _, _, first_part = jquery_raw.partition('{') data_part = u'{{{0:s}'.format(first_part) elif '({' in jquery_raw: _, _, first_part = jquery_raw.partition('(') data_part, _, _ = first_part.rpartition(')') if not data_part: return {} try: data_dict = json.loads(data_part) except ValueError: return {} return data_dict def ParseChatData(data): """Parse a chat comment data dict and return a parsed one back. Args: data: A dict object that is parsed from the record. Returns: A dict object to store the results in. """ data_store = {} if 'body' in data: body = data.get('body', '').replace('\n', ' ') if body.startswith('//') and '{' in body: body_dict = ExtractJQuery(body) title, _, _ = body.partition('{') body = u'{0:s} <{1!s}>'.format(title[2:], DictToList(body_dict)) else: body = 'No text.' data_store['text'] = body room = data.get('rooms', None) if not room: room = data.get('room', None) if room: data_store['room'] = room data_store['id'] = data.get('id', None) user = data.get('user', None) if user: try: user_sid = int(user) data_store['sid'] = user_sid except (ValueError, TypeError): data_store['user'] = user return data_store class MacKeeperCacheEvent(event.EventObject): """Convenience class for a MacKeeper Cache event.""" DATA_TYPE = 'mackeeper:cache' def __init__(self, timestamp, description, identifier, url, data_dict): """Initializes the event object. Args: timestamp: A timestamp as a number of milliseconds since Epoch or as a UTC string. description: The description of the cache entry. identifier: The row identifier. url: The MacKeeper URL value that is stored in every event. data_dict: A dict object with the descriptive information. """ super(MacKeeperCacheEvent, self).__init__() # Two different types of timestamps stored in log files. if type(timestamp) in (int, long): self.timestamp = timelib.Timestamp.FromJavaTime(timestamp) else: self.timestamp = timelib.Timestamp.FromTimeString(timestamp) self.timestamp_desc = eventdata.EventTimestamp.ADDED_TIME self.description = description self.offset = identifier self.text = data_dict.get('text', None) self.user_sid = data_dict.get('sid', None) self.user_name = data_dict.get('user', None) self.event_type = data_dict.get('event_type', None) self.room = data_dict.get('room', None) self.record_id = data_dict.get('id', None) self.url = url class MacKeeperCachePlugin(interface.SQLitePlugin): """Plugin for the MacKeeper Cache database file.""" NAME = 'mackeeper_cache' DESCRIPTION = u'Parser for MacKeeper Cache SQLite database files.' # Define the needed queries. QUERIES = [(( 'SELECT d.entry_ID AS id, d.receiver_data AS data, r.request_key, ' 'r.time_stamp AS time_string FROM cfurl_cache_receiver_data d, ' 'cfurl_cache_response r WHERE r.entry_ID = ' 'd.entry_ID'), 'ParseReceiverData')] # The required tables. REQUIRED_TABLES = frozenset([ 'cfurl_cache_blob_data', 'cfurl_cache_receiver_data', 'cfurl_cache_response']) def ParseReceiverData( self, parser_context, row, file_entry=None, parser_chain=None, query=None, **unused_kwargs): """Parses a single row from the receiver and cache response table. Args: parser_context: A parser context object (instance of ParserContext). row: The row resulting from the query. file_entry: Optional file entry object (instance of dfvfs.FileEntry). The default is None. parser_chain: Optional string containing the parsing chain up to this point. The default is None. query: Optional query string. The default is None. """ data = {} key_url = row['request_key'] data_dict = {} description = 'MacKeeper Entry' # Check the URL, since that contains vital information about the type of # event we are dealing with. if key_url.endswith('plist'): description = 'Configuration Definition' data['text'] = 'Plist content added to cache.' elif key_url.startswith('http://event.zeobit.com'): description = 'MacKeeper Event' try: _, _, part = key_url.partition('?') data['text'] = part.replace('&', ' ') except UnicodeDecodeError: data['text'] = 'N/A' elif key_url.startswith('http://account.zeobit.com'): description = 'Account Activity' _, _, activity = key_url.partition('#') if activity: data['text'] = u'Action started: {0:s}'.format(activity) else: data['text'] = u'Unknown activity.' elif key_url.startswith('http://support.') and 'chat' in key_url: description = 'Chat ' try: jquery = unicode(row['data']) except UnicodeDecodeError: jquery = '' data_dict = ExtractJQuery(jquery) data = ParseChatData(data_dict) data['entry_type'] = data_dict.get('type', '') if data['entry_type'] == 'comment': description += 'Comment' elif data['entry_type'] == 'outgoing': description += 'Outgoing Message' elif data['entry_type'] == 'incoming': description += 'Incoming Message' else: # Empty or not known entry type, generic status message. description += 'Entry' data['text'] = u';'.join(DictToList(data_dict)) if not data['text']: data['text'] = 'No additional data.' event_object = MacKeeperCacheEvent( row['time_string'], description, row['id'], key_url, data) parser_context.ProduceEvent( event_object, query=query, parser_chain=parser_chain, file_entry=file_entry) sqlite.SQLiteParser.RegisterPlugin(MacKeeperCachePlugin)