OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
3 """The image export front-end.""" | 3 """The image export command line tool.""" |
4 | 4 |
5 import argparse | |
6 import logging | 5 import logging |
7 import os | |
8 import sys | 6 import sys |
9 import textwrap | |
10 | 7 |
11 from plaso.cli import storage_media_tool | 8 from plaso.cli import image_export_tool |
12 from plaso.frontend import image_export | |
13 from plaso.lib import errors | 9 from plaso.lib import errors |
14 | 10 |
15 | 11 |
16 class ImageExportTool(storage_media_tool.StorageMediaTool): | |
17 """Class that implements the image export CLI tool. | |
18 | |
19 Attributes: | |
20 has_filters (bool): True if filters have been specified via the options. | |
21 list_signature_identifiers (bool): True if information about the signature | |
22 identifiers should be shown. | |
23 """ | |
24 | |
25 NAME = u'image_export' | |
26 DESCRIPTION = ( | |
27 u'This is a simple collector designed to export files inside an ' | |
28 u'image, both within a regular RAW image as well as inside a VSS. ' | |
29 u'The tool uses a collection filter that uses the same syntax as a ' | |
30 u'targeted plaso filter.') | |
31 | |
32 EPILOG = u'And that is how you export files, plaso style.' | |
33 | |
34 _SOURCE_OPTION = u'image' | |
35 | |
36 def __init__(self, input_reader=None, output_writer=None): | |
37 """Initializes the CLI tool object. | |
38 | |
39 Args: | |
40 input_reader (Optional[InputReader]): input reader, where None indicates | |
41 that the stdin input reader should be used. | |
42 output_writer (Optional[OutputWriter]): output writer, where None | |
43 indicates that the stdout output writer should be used. | |
44 """ | |
45 super(ImageExportTool, self).__init__( | |
46 input_reader=input_reader, output_writer=output_writer) | |
47 self._destination_path = None | |
48 self._filter_file = None | |
49 self._front_end = image_export.ImageExportFrontend() | |
50 self._skip_duplicates = True | |
51 self.has_filters = False | |
52 self.list_signature_identifiers = False | |
53 | |
54 def ListSignatureIdentifiers(self): | |
55 """Lists the signature identifier. | |
56 | |
57 Raises: | |
58 BadConfigOption: if the data location is invalid. | |
59 """ | |
60 if not self._data_location: | |
61 raise errors.BadConfigOption(u'Missing data location.') | |
62 | |
63 path = os.path.join(self._data_location, u'signatures.conf') | |
64 if not os.path.exists(path): | |
65 raise errors.BadConfigOption( | |
66 u'No such format specification file: {0:s}'.format(path)) | |
67 | |
68 try: | |
69 specification_store = self._front_end.ReadSpecificationFile(path) | |
70 except IOError as exception: | |
71 raise errors.BadConfigOption(( | |
72 u'Unable to read format specification file: {0:s} with error: ' | |
73 u'{1:s}').format(path, exception)) | |
74 | |
75 identifiers = [] | |
76 for format_specification in specification_store.specifications: | |
77 identifiers.append(format_specification.identifier) | |
78 | |
79 self._output_writer.Write(u'Available signature identifiers:\n') | |
80 self._output_writer.Write( | |
81 u'\n'.join(textwrap.wrap(u', '.join(sorted(identifiers)), 79))) | |
82 self._output_writer.Write(u'\n\n') | |
83 | |
84 def ParseArguments(self): | |
85 """Parses the command line arguments. | |
86 | |
87 Returns: | |
88 bool: True if the arguments were successfully parsed. | |
89 """ | |
90 self._ConfigureLogging() | |
91 | |
92 argument_parser = argparse.ArgumentParser( | |
93 description=self.DESCRIPTION, epilog=self.EPILOG, add_help=False, | |
94 formatter_class=argparse.RawDescriptionHelpFormatter) | |
95 | |
96 self.AddBasicOptions(argument_parser) | |
97 self.AddInformationalOptions(argument_parser) | |
98 self.AddDataLocationOption(argument_parser) | |
99 self.AddLogFileOptions(argument_parser) | |
100 | |
101 argument_parser.add_argument( | |
102 u'-w', u'--write', action=u'store', dest=u'path', type=str, | |
103 metavar=u'PATH', default=u'export', help=( | |
104 u'The directory in which extracted files should be stored.')) | |
105 | |
106 self.AddFilterOptions(argument_parser) | |
107 argument_parser.add_argument( | |
108 u'--date-filter', u'--date_filter', action=u'append', type=str, | |
109 dest=u'date_filters', metavar=u'TYPE_START_END', default=[], help=( | |
110 u'Filter based on file entry date and time ranges. This parameter ' | |
111 u'is formatted as "TIME_VALUE,START_DATE_TIME,END_DATE_TIME" where ' | |
112 u'TIME_VALUE defines which file entry timestamp the filter applies ' | |
113 u'to e.g. atime, ctime, crtime, bkup, etc. START_DATE_TIME and ' | |
114 u'END_DATE_TIME define respectively the start and end of the date ' | |
115 u'time range. A date time range requires at minimum start or end ' | |
116 u'to time of the boundary and END defines the end time. Both ' | |
117 u'timestamps be set. The date time values are formatted as: ' | |
118 u'YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ' | |
119 u'ranging from 0 to 9 and the seconds fraction can be either 3 ' | |
120 u'or 6 digits. The time of day, seconds fraction and timezone ' | |
121 u'offset are optional. The default timezone is UTC. E.g. "atime, ' | |
122 u'2013-01-01 23:12:14, 2013-02-23". This parameter can be repeated ' | |
123 u'as needed to add additional date date boundaries, e.g. once for ' | |
124 u'atime, once for crtime, etc.')) | |
125 | |
126 argument_parser.add_argument( | |
127 u'-x', u'--extensions', dest=u'extensions_string', action=u'store', | |
128 type=str, metavar=u'EXTENSIONS', help=( | |
129 u'Filter based on file name extensions. This option accepts ' | |
130 u'multiple multiple comma separated values e.g. "csv,docx,pst".')) | |
131 | |
132 argument_parser.add_argument( | |
133 u'--names', dest=u'names_string', action=u'store', | |
134 type=str, metavar=u'NAMES', help=( | |
135 u'If the purpose is to find all files given a certain names ' | |
136 u'this options should be used. This option accepts a comma ' | |
137 u'separated string denoting all file names, e.g. -x ' | |
138 u'"NTUSER.DAT,UsrClass.dat".')) | |
139 | |
140 argument_parser.add_argument( | |
141 u'--signatures', dest=u'signature_identifiers', action=u'store', | |
142 type=str, metavar=u'IDENTIFIERS', help=( | |
143 u'Filter based on file format signature identifiers. This option ' | |
144 u'accepts multiple comma separated values e.g. "esedb,lnk". ' | |
145 u'Use "list" to show an overview of the supported file format ' | |
146 u'signatures.')) | |
147 | |
148 argument_parser.add_argument( | |
149 u'--include_duplicates', dest=u'include_duplicates', | |
150 action=u'store_true', default=False, help=( | |
151 u'If extraction from VSS is enabled, by default a digest hash ' | |
152 u'is calculated for each file. These hashes are compared to the ' | |
153 u'previously exported files and duplicates are skipped. Use ' | |
154 u'this option to include duplicate files in the export.')) | |
155 | |
156 self.AddStorageMediaImageOptions(argument_parser) | |
157 self.AddVSSProcessingOptions(argument_parser) | |
158 | |
159 argument_parser.add_argument( | |
160 self._SOURCE_OPTION, nargs='?', action=u'store', metavar=u'IMAGE', | |
161 default=None, type=str, help=( | |
162 u'The full path to the image file that we are about to extract ' | |
163 u'files from, it should be a raw image or another image that ' | |
164 u'plaso supports.')) | |
165 | |
166 try: | |
167 options = argument_parser.parse_args() | |
168 except UnicodeEncodeError: | |
169 # If we get here we are attempting to print help in a non-Unicode | |
170 # terminal. | |
171 self._output_writer.Write(u'') | |
172 self._output_writer.Write(argument_parser.format_help()) | |
173 return False | |
174 | |
175 try: | |
176 self.ParseOptions(options) | |
177 except errors.BadConfigOption as exception: | |
178 self._output_writer.Write(u'ERROR: {0:s}'.format(exception)) | |
179 self._output_writer.Write(u'') | |
180 self._output_writer.Write(argument_parser.format_usage()) | |
181 return False | |
182 | |
183 return True | |
184 | |
185 def ParseOptions(self, options): | |
186 """Parses the options and initializes the front-end. | |
187 | |
188 Args: | |
189 options (argparse.Namespace): command line arguments. | |
190 | |
191 Raises: | |
192 BadConfigOption: if the options are invalid. | |
193 """ | |
194 # The data location is required to list signatures. | |
195 self._ParseDataLocationOption(options) | |
196 | |
197 # Check the list options first otherwise required options will raise. | |
198 signature_identifiers = self.ParseStringOption( | |
199 options, u'signature_identifiers') | |
200 if signature_identifiers == u'list': | |
201 self.list_signature_identifiers = True | |
202 | |
203 if self.list_signature_identifiers: | |
204 return | |
205 | |
206 super(ImageExportTool, self).ParseOptions(options) | |
207 | |
208 format_string = ( | |
209 u'%(asctime)s [%(levelname)s] (%(processName)-10s) PID:%(process)d ' | |
210 u'<%(module)s> %(message)s') | |
211 | |
212 if self._debug_mode: | |
213 logging_level = logging.DEBUG | |
214 elif self._quiet_mode: | |
215 logging_level = logging.WARNING | |
216 else: | |
217 logging_level = logging.INFO | |
218 | |
219 self.ParseLogFileOptions(options) | |
220 self._ConfigureLogging( | |
221 filename=self._log_file, format_string=format_string, | |
222 log_level=logging_level) | |
223 | |
224 self._destination_path = self.ParseStringOption( | |
225 options, u'path', default_value=u'export') | |
226 | |
227 self._ParseFilterOptions(options) | |
228 | |
229 if (getattr(options, u'no_vss', False) or | |
230 getattr(options, u'include_duplicates', False)): | |
231 self._skip_duplicates = False | |
232 | |
233 date_filters = getattr(options, u'date_filters', None) | |
234 try: | |
235 self._front_end.ParseDateFilters(date_filters) | |
236 except ValueError as exception: | |
237 raise errors.BadConfigOption(exception) | |
238 | |
239 extensions_string = self.ParseStringOption(options, u'extensions_string') | |
240 self._front_end.ParseExtensionsString(extensions_string) | |
241 | |
242 names_string = getattr(options, u'names_string', None) | |
243 self._front_end.ParseNamesString(names_string) | |
244 | |
245 if not self._data_location: | |
246 logging.warning(u'Unable to automatically determine data location.') | |
247 | |
248 signature_identifiers = getattr(options, u'signature_identifiers', None) | |
249 try: | |
250 self._front_end.ParseSignatureIdentifiers( | |
251 self._data_location, signature_identifiers) | |
252 except (IOError, ValueError) as exception: | |
253 raise errors.BadConfigOption(exception) | |
254 | |
255 if self._filter_file: | |
256 self.has_filters = True | |
257 else: | |
258 self.has_filters = self._front_end.HasFilters() | |
259 | |
260 def PrintFilterCollection(self): | |
261 """Prints the filter collection.""" | |
262 self._front_end.PrintFilterCollection(self._output_writer) | |
263 | |
264 def ProcessSources(self): | |
265 """Processes the sources. | |
266 | |
267 Raises: | |
268 SourceScannerError: if the source scanner could not find a supported | |
269 file system. | |
270 UserAbort: if the user initiated an abort. | |
271 """ | |
272 self.ScanSource() | |
273 | |
274 self._output_writer.Write(u'Export started.\n') | |
275 | |
276 self._front_end.ProcessSources( | |
277 self._source_path_specs, self._destination_path, self._output_writer, | |
278 filter_file=self._filter_file, skip_duplicates=self._skip_duplicates) | |
279 | |
280 self._output_writer.Write(u'Export completed.\n') | |
281 self._output_writer.Write(u'\n') | |
282 | |
283 | |
284 def Main(): | 12 def Main(): |
285 """The main function.""" | 13 """The main function.""" |
286 tool = ImageExportTool() | 14 tool = image_export_tool.ImageExportTool() |
287 | 15 |
288 if not tool.ParseArguments(): | 16 if not tool.ParseArguments(): |
289 return False | 17 return False |
290 | 18 |
291 if tool.list_signature_identifiers: | 19 if tool.list_signature_identifiers: |
292 tool.ListSignatureIdentifiers() | 20 tool.ListSignatureIdentifiers() |
293 return True | 21 return True |
294 | 22 |
295 if not tool.has_filters: | 23 if not tool.has_filters: |
296 logging.warning(u'No filter defined exporting all files.') | 24 logging.warning(u'No filter defined exporting all files.') |
(...skipping 20 matching lines...) Expand all Loading... |
317 return False | 45 return False |
318 | 46 |
319 return True | 47 return True |
320 | 48 |
321 | 49 |
322 if __name__ == '__main__': | 50 if __name__ == '__main__': |
323 if not Main(): | 51 if not Main(): |
324 sys.exit(1) | 52 sys.exit(1) |
325 else: | 53 else: |
326 sys.exit(0) | 54 sys.exit(0) |
OLD | NEW |