269 lines
9.3 KiB
Python
Raw Normal View History

2020-04-06 18:48:34 +02:00
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2012 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 Google Drive snaphots.
The Google Drive snapshots are stored in SQLite database files named
snapshot.db.
"""
from plaso.events import time_events
from plaso.lib import eventdata
from plaso.parsers import sqlite
from plaso.parsers.sqlite_plugins import interface
__author__ = 'David Nides (david.nides@gmail.com)'
class GoogleDriveSnapshotCloudEntryEvent(time_events.PosixTimeEvent):
"""Convenience class for a Google Drive snapshot cloud entry."""
DATA_TYPE = 'gdrive:snapshot:cloud_entry'
# TODO: this could be moved to the formatter.
# The following definition for values can be found on Patrick Olson's blog:
# http://www.sysforensics.org/2012/05/google-drive-forensics-notes.html
_DOC_TYPES = {
0: u'FOLDER',
1: u'FILE',
2: u'PRESENTATION',
3: u'UNKNOWN',
4: u'SPREADSHEET',
5: u'DRAWING',
6: u'DOCUMENT',
7: u'TABLE',
}
def __init__(self, posix_time, usage, url, path, size, doc_type, shared):
"""Initializes the event.
Args:
posix_time: The POSIX time value.
usage: The description of the usage of the time value.
url: The URL of the file as in the cloud.
path: The path of the file.
size: The size of the file.
doc_type: Integer value containing the document type.
shared: A string indicating whether or not this is a shared document.
"""
super(GoogleDriveSnapshotCloudEntryEvent, self).__init__(
posix_time, usage)
self.url = url
self.path = path
self.size = size
self.document_type = self._DOC_TYPES.get(doc_type, u'UNKNOWN')
self.shared = shared
class GoogleDriveSnapshotLocalEntryEvent(time_events.PosixTimeEvent):
"""Convenience class for a Google Drive snapshot local entry event."""
DATA_TYPE = 'gdrive:snapshot:local_entry'
def __init__(self, posix_time, local_path, size):
"""Initializes the event object.
Args:
posix_time: The POSIX time value.
local_path: The local path of the file.
size: The size of the file.
"""
super(GoogleDriveSnapshotLocalEntryEvent, self).__init__(
posix_time, eventdata.EventTimestamp.MODIFICATION_TIME)
self.path = local_path
self.size = size
class GoogleDrivePlugin(interface.SQLitePlugin):
"""SQLite plugin for Google Drive snapshot.db files."""
NAME = 'google_drive'
DESCRIPTION = u'Parser for Google Drive SQLite database files.'
# Define the needed queries.
QUERIES = [
((u'SELECT e.resource_id, e.filename, e.modified, e.created, e.size, '
u'e.doc_type, e.shared, e.checksum, e.url, r.parent_resource_id FROM '
u'cloud_entry AS e, cloud_relations AS r WHERE r.child_resource_id = '
u'e.resource_id AND e.modified IS NOT NULL;'), 'ParseCloudEntryRow'),
((u'SELECT inode_number, filename, modified, checksum, size FROM '
u'local_entry WHERE modified IS NOT NULL;'), 'ParseLocalEntryRow')]
# The required tables.
REQUIRED_TABLES = frozenset([
'cloud_entry', 'cloud_relations', 'local_entry', 'local_relations',
'mapping', 'overlay_status'])
# Queries used to build cache.
LOCAL_PATH_CACHE_QUERY = (
u'SELECT r.child_inode_number, r.parent_inode_number, e.filename FROM '
u'local_relations AS r, local_entry AS e WHERE r.child_inode_number = '
u'e.inode_number')
CLOUD_PATH_CACHE_QUERY = (
u'SELECT e.filename, e.resource_id, r.parent_resource_id AS parent '
u'FROM cloud_entry AS e, cloud_relations AS r WHERE e.doc_type = 0 '
u'AND e.resource_id = r.child_resource_id')
def GetLocalPath(self, inode, cache, database):
"""Return local path for a given inode.
Args:
inode: The inode number for the file.
cache: A cache object (instance of SQLiteCache).
database: A database object (instance of SQLiteDatabase).
Returns:
A full path, including the filename of the given inode value.
"""
local_path = cache.GetResults('local_path')
if not local_path:
cursor = database.cursor
results = cursor.execute(self.LOCAL_PATH_CACHE_QUERY)
cache.CacheQueryResults(
results, 'local_path', 'child_inode_number',
('parent_inode_number', 'filename'))
local_path = cache.GetResults('local_path')
parent, path = local_path.get(inode, [None, None])
# TODO: Read the local_sync_root from the sync_config.db and use that
# for a root value.
root_value = u'%local_sync_root%/'
if not path:
return root_value
paths = []
while path:
paths.append(path)
parent, path = local_path.get(parent, [None, None])
if not paths:
return root_value
# Paths are built top level to root so we need to reverse the list to
# represent them in the traditional order.
paths.reverse()
return root_value + u'/'.join(paths)
def GetCloudPath(self, resource_id, cache, database):
"""Return cloud path given a resource id.
Args:
resource_id: The resource_id for the file.
cache: The local cache object.
database: A database object (instance of SQLiteDatabase).
Returns:
A full path to the resource value.
"""
cloud_path = cache.GetResults('cloud_path')
if not cloud_path:
cursor = database.cursor
results = cursor.execute(self.CLOUD_PATH_CACHE_QUERY)
cache.CacheQueryResults(
results, 'cloud_path', 'resource_id', ('filename', 'parent'))
cloud_path = cache.GetResults('cloud_path')
if resource_id == u'folder:root':
return u'/'
paths = []
parent_path, parent_id = cloud_path.get(resource_id, [u'', u''])
while parent_path:
if parent_path == u'folder:root':
break
paths.append(parent_path)
parent_path, parent_id = cloud_path.get(parent_id, [u'', u''])
if not paths:
return u'/'
# Paths are built top level to root so we need to reverse the list to
# represent them in the traditional order.
paths.reverse()
return u'/{0:s}/'.format(u'/'.join(paths))
def ParseCloudEntryRow(
self, parser_context, row, file_entry=None, parser_chain=None, query=None,
cache=None, database=None, **unused_kwargs):
"""Parses a cloud entry row.
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.
cache: The local cache object.
database: The database object.
"""
cloud_path = self.GetCloudPath(row['parent_resource_id'], cache, database)
cloud_filename = u'{0:s}{1:s}'.format(cloud_path, row['filename'])
if row['shared']:
shared = 'Shared'
else:
shared = 'Private'
event_object = GoogleDriveSnapshotCloudEntryEvent(
row['modified'], eventdata.EventTimestamp.MODIFICATION_TIME,
row['url'], cloud_filename, row['size'], row['doc_type'], shared)
parser_context.ProduceEvent(
event_object, query=query, parser_chain=parser_chain,
file_entry=file_entry)
if row['created']:
event_object = GoogleDriveSnapshotCloudEntryEvent(
row['created'], eventdata.EventTimestamp.CREATION_TIME,
row['url'], cloud_filename, row['size'], row['doc_type'], shared)
parser_context.ProduceEvent(
event_object, query=query, parser_chain=parser_chain,
file_entry=file_entry)
def ParseLocalEntryRow(
self, parser_context, row, file_entry=None, parser_chain=None, query=None,
cache=None, database=None, **unused_kwargs):
"""Parses a local entry row.
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.
cache: The local cache object (instance of SQLiteCache).
database: A database object (instance of SQLiteDatabase).
"""
local_path = self.GetLocalPath(row['inode_number'], cache, database)
event_object = GoogleDriveSnapshotLocalEntryEvent(
row['modified'], local_path, row['size'])
parser_context.ProduceEvent(
event_object, query=query, parser_chain=parser_chain,
file_entry=file_entry)
sqlite.SQLiteParser.RegisterPlugin(GoogleDrivePlugin)