OLD | NEW |
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 """Parser for utmpx files.""" | 2 """Parser for utmpx files.""" |
3 | 3 |
4 from __future__ import unicode_literals | 4 from __future__ import unicode_literals |
5 | 5 |
6 # TODO: Add support for other implementations than MacOS. | |
7 # The parser should be checked against IOS UTMPX file. | |
8 | |
9 import construct | |
10 | |
11 from dfdatetime import posix_time as dfdatetime_posix_time | 6 from dfdatetime import posix_time as dfdatetime_posix_time |
12 | 7 |
13 from plaso.containers import events | 8 from plaso.containers import events |
14 from plaso.containers import time_events | 9 from plaso.containers import time_events |
15 from plaso.lib import errors | 10 from plaso.lib import errors |
16 from plaso.lib import definitions | 11 from plaso.lib import definitions |
17 from plaso.parsers import interface | 12 from plaso.lib import specification |
18 from plaso.parsers import logger | 13 from plaso.parsers import dtfabric_parser |
19 from plaso.parsers import manager | 14 from plaso.parsers import manager |
20 | 15 |
21 | 16 |
22 class UtmpxMacOSEventData(events.EventData): | 17 class UtmpxMacOSEventData(events.EventData): |
23 """MacOS utmpx event data. | 18 """MacOS utmpx event data. |
24 | 19 |
25 Attributes: | 20 Attributes: |
26 computer_name (str): name of the host or IP address. | 21 hostname (str): hostname or IP address. |
27 status_type (int): terminal status type. | 22 pid (int): process identifier (PID). |
28 terminal (str): name of the terminal. | 23 terminal (str): name of the terminal. |
29 user (str): active user name. | 24 terminal_identifier (int): inittab identifier. |
| 25 type (int): type of login. |
| 26 username (str): user name. |
30 """ | 27 """ |
31 | 28 |
32 DATA_TYPE = 'mac:utmpx:event' | 29 DATA_TYPE = 'mac:utmpx:event' |
33 | 30 |
34 def __init__(self): | 31 def __init__(self): |
35 """Initializes event data.""" | 32 """Initializes event data.""" |
36 super(UtmpxMacOSEventData, self).__init__(data_type=self.DATA_TYPE) | 33 super(UtmpxMacOSEventData, self).__init__(data_type=self.DATA_TYPE) |
37 self.computer_name = None | 34 self.hostname = None |
38 self.status_type = None | 35 self.pid = None |
39 self.terminal = None | 36 self.terminal = None |
40 self.user = None | 37 self.terminal_identifier = None |
| 38 self.type = None |
| 39 self.username = None |
41 | 40 |
42 | 41 |
43 class UtmpxParser(interface.FileObjectParser): | 42 class UtmpxParser(dtfabric_parser.DtFabricBaseParser): |
44 """Parser for UTMPX files.""" | 43 """Parser for Mac OS X 10.5 utmpx files.""" |
45 | 44 |
46 NAME = 'utmpx' | 45 NAME = 'utmpx' |
47 DESCRIPTION = 'Parser for UTMPX files.' | 46 DESCRIPTION = 'Parser for Mac OS X 10.5 utmpx files.' |
48 | 47 |
49 # INFO: Type is suppose to be a short (2 bytes), | 48 _DEFINITION_FILE = 'utmp.yaml' |
50 # however if we analyze the file it is always | |
51 # byte follow by 3 bytes with \x00 value. | |
52 _UTMPX_ENTRY = construct.Struct( | |
53 'utmpx_mac', | |
54 construct.String('user', 256), | |
55 construct.ULInt32('id'), | |
56 construct.String('tty_name', 32), | |
57 construct.ULInt32('pid'), | |
58 construct.ULInt16('status_type'), | |
59 construct.ULInt16('unknown'), | |
60 construct.ULInt32('timestamp'), | |
61 construct.ULInt32('microseconds'), | |
62 construct.String('hostname', 256), | |
63 construct.Padding(64)) | |
64 | 49 |
65 _UTMPX_ENTRY_SIZE = _UTMPX_ENTRY.sizeof() | 50 _SUPPORTED_TYPES = frozenset(range(0, 12)) |
66 | 51 |
67 _STATUS_TYPE_SIGNATURE = 10 | 52 _FILE_HEADER_USERNAME = 'utmpx-1.00' |
| 53 _FILE_HEADER_TYPE = 10 |
68 | 54 |
69 def _ReadEntry(self, parser_mediator, file_object): | 55 def _ReadEntry(self, parser_mediator, file_object, file_offset): |
70 """Reads an UTMPX entry. | 56 """Reads an utmpx entry. |
71 | 57 |
72 Args: | 58 Args: |
73 parser_mediator (ParserMediator): mediates interactions between parsers | 59 parser_mediator (ParserMediator): mediates interactions between parsers |
74 and other components, such as storage and dfvfs. | 60 and other components, such as storage and dfvfs. |
75 file_object (dfvfs.FileIO): a file-like object. | 61 file_object (dfvfs.FileIO): a file-like object. |
| 62 file_offset (int): offset of the data relative from the start of |
| 63 the file-like object. |
76 | 64 |
77 Returns: | 65 Returns: |
78 bool: True if the UTMPX entry was successfully read. | 66 tuple: contains: |
| 67 |
| 68 int: timestamp, which contains the number of microseconds |
| 69 since January 1, 1970, 00:00:00 UTC. |
| 70 UtmpxMacOSEventData: event data of the utmpx entry read. |
| 71 |
| 72 Raises: |
| 73 ParseError: if the entry cannot be parsed. |
79 """ | 74 """ |
80 data = file_object.read(self._UTMPX_ENTRY_SIZE) | 75 entry_map = self._GetDataTypeMap('macosx_utmpx_entry') |
81 if len(data) != self._UTMPX_ENTRY_SIZE: | |
82 return False | |
83 | 76 |
84 try: | 77 try: |
85 entry_struct = self._UTMPX_ENTRY.parse(data) | 78 entry, _ = self._ReadStructureFromFileObject( |
86 except (IOError, construct.FieldError) as exception: | 79 file_object, file_offset, entry_map) |
87 logger.warning( | 80 except (ValueError, errors.ParseError) as exception: |
88 'Unable to parse MacOS UTMPX entry with error: {0!s}'.format( | 81 raise errors.ParseError(( |
89 exception)) | 82 'Unable to parse utmpx entry at offset: 0x{0:08x} with error: ' |
90 return False | 83 '{1!s}.').format(file_offset, exception)) |
91 | 84 |
92 user, _, _ = entry_struct.user.partition(b'\x00') | 85 if entry.type not in self._SUPPORTED_TYPES: |
93 if not user: | 86 raise errors.UnableToParseFile('Unsupported type: {0:d}'.format( |
94 user = 'N/A' | 87 entry.type)) |
95 | 88 |
96 terminal, _, _ = entry_struct.tty_name.partition(b'\x00') | 89 encoding = parser_mediator.codepage or 'utf8' |
97 if not terminal: | |
98 terminal = 'N/A' | |
99 | 90 |
100 computer_name, _, _ = entry_struct.hostname.partition(b'\x00') | 91 try: |
101 if not computer_name: | 92 username = entry.username.split(b'\x00')[0] |
102 computer_name = 'localhost' | 93 username = username.decode(encoding) |
| 94 except UnicodeDecodeError: |
| 95 parser_mediator.ProduceExtractionError('unable to decode username string') |
| 96 username = None |
| 97 |
| 98 try: |
| 99 terminal = entry.terminal.split(b'\x00')[0] |
| 100 terminal = terminal.decode(encoding) |
| 101 except UnicodeDecodeError: |
| 102 parser_mediator.ProduceExtractionError('unable to decode terminal string') |
| 103 terminal = None |
| 104 |
| 105 if terminal == '~': |
| 106 terminal = 'system boot' |
| 107 |
| 108 try: |
| 109 hostname = entry.hostname.split(b'\x00')[0] |
| 110 hostname = hostname.decode(encoding) |
| 111 except UnicodeDecodeError: |
| 112 parser_mediator.ProduceExtractionError('unable to decode hostname string') |
| 113 hostname = None |
| 114 |
| 115 if not hostname: |
| 116 hostname = 'localhost' |
103 | 117 |
104 event_data = UtmpxMacOSEventData() | 118 event_data = UtmpxMacOSEventData() |
105 event_data.computer_name = computer_name | 119 event_data.hostname = hostname |
106 event_data.offset = file_object.tell() | 120 event_data.pid = entry.pid |
107 event_data.status_type = entry_struct.status_type | 121 event_data.offset = file_offset |
108 event_data.terminal = terminal | 122 event_data.terminal = terminal |
109 event_data.user = user | 123 event_data.terminal_identifier = entry.terminal_identifier |
| 124 event_data.type = entry.type |
| 125 event_data.username = username |
110 | 126 |
111 timestamp = (entry_struct.timestamp * 1000000) + entry_struct.microseconds | 127 timestamp = entry.microseconds + ( |
112 date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( | 128 entry.timestamp * definitions.MICROSECONDS_PER_SECOND) |
113 timestamp=timestamp) | 129 return timestamp, event_data |
114 event = time_events.DateTimeValuesEvent( | |
115 date_time, definitions.TIME_DESCRIPTION_START) | |
116 parser_mediator.ProduceEventWithEventData(event, event_data) | |
117 | 130 |
118 return True | 131 @classmethod |
119 | 132 def GetFormatSpecification(cls): |
120 def _VerifyStructure(self, file_object): | 133 """Retrieves the format specification. |
121 """Verify that we are dealing with an UTMPX entry. | |
122 | |
123 Args: | |
124 file_object (dfvfs.FileIO): a file-like object. | |
125 | 134 |
126 Returns: | 135 Returns: |
127 bool: True if it is a UTMPX entry or False otherwise. | 136 FormatSpecification: format specification. |
128 """ | 137 """ |
129 # First entry is a SIGNAL entry of the file ("header"). | 138 format_specification = specification.FormatSpecification(cls.NAME) |
130 try: | 139 format_specification.AddNewSignature(b'utmpx-1.00\x00', offset=0) |
131 header_struct = self._UTMPX_ENTRY.parse_stream(file_object) | 140 return format_specification |
132 except (IOError, construct.FieldError): | |
133 return False | |
134 user, _, _ = header_struct.user.partition(b'\x00') | |
135 | |
136 # The UTMPX_ENTRY structure will often successfully compile on various | |
137 # structures, such as binary plist files, and thus we need to do some | |
138 # additional validation. The first one is to check if the user name | |
139 # can be converted into a Unicode string, otherwise we can assume | |
140 # we are dealing with non UTMPX data. | |
141 try: | |
142 user.decode('utf-8') | |
143 except UnicodeDecodeError: | |
144 return False | |
145 | |
146 if user != b'utmpx-1.00': | |
147 return False | |
148 if header_struct.status_type != self._STATUS_TYPE_SIGNATURE: | |
149 return False | |
150 if (header_struct.timestamp != 0 or header_struct.microseconds != 0 or | |
151 header_struct.pid != 0): | |
152 return False | |
153 tty_name, _, _ = header_struct.tty_name.partition(b'\x00') | |
154 hostname, _, _ = header_struct.hostname.partition(b'\x00') | |
155 if tty_name or hostname: | |
156 return False | |
157 | |
158 return True | |
159 | 141 |
160 def ParseFileObject(self, parser_mediator, file_object, **kwargs): | 142 def ParseFileObject(self, parser_mediator, file_object, **kwargs): |
161 """Parses an UTMPX file-like object. | 143 """Parses an UTMPX file-like object. |
162 | 144 |
163 Args: | 145 Args: |
164 parser_mediator (ParserMediator): mediates interactions between parsers | 146 parser_mediator (ParserMediator): mediates interactions between parsers |
165 and other components, such as storage and dfvfs. | 147 and other components, such as storage and dfvfs. |
166 file_object (dfvfs.FileIO): a file-like object. | 148 file_object (dfvfs.FileIO): a file-like object. |
167 | 149 |
168 Raises: | 150 Raises: |
169 UnableToParseFile: when the file cannot be parsed. | 151 UnableToParseFile: when the file cannot be parsed. |
170 """ | 152 """ |
171 if not self._VerifyStructure(file_object): | 153 file_offset = 0 |
| 154 |
| 155 try: |
| 156 timestamp, event_data = self._ReadEntry( |
| 157 parser_mediator, file_object, file_offset) |
| 158 except errors.ParseError as exception: |
172 raise errors.UnableToParseFile( | 159 raise errors.UnableToParseFile( |
173 'The file is not an UTMPX file.') | 160 'Unable to parse utmpx file header with error: {0!s}'.format( |
| 161 exception)) |
174 | 162 |
175 while self._ReadEntry(parser_mediator, file_object): | 163 if event_data.username != self._FILE_HEADER_USERNAME: |
176 pass | 164 raise errors.UnableToParseFile( |
| 165 'Unable to parse utmpx file header with error: unsupported username') |
| 166 |
| 167 if event_data.type != self._FILE_HEADER_TYPE: |
| 168 raise errors.UnableToParseFile( |
| 169 'Unable to parse utmp file header with error: unsupported type of ' |
| 170 'login') |
| 171 |
| 172 file_offset = file_object.tell() |
| 173 file_size = file_object.get_size() |
| 174 |
| 175 while file_offset < file_size: |
| 176 if parser_mediator.abort: |
| 177 break |
| 178 |
| 179 try: |
| 180 timestamp, event_data = self._ReadEntry( |
| 181 parser_mediator, file_object, file_offset) |
| 182 except errors.ParseError as exception: |
| 183 break |
| 184 |
| 185 date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( |
| 186 timestamp=timestamp) |
| 187 event = time_events.DateTimeValuesEvent( |
| 188 date_time, definitions.TIME_DESCRIPTION_START) |
| 189 parser_mediator.ProduceEventWithEventData(event, event_data) |
| 190 |
| 191 file_offset = file_object.tell() |
177 | 192 |
178 | 193 |
179 manager.ParsersManager.RegisterParser(UtmpxParser) | 194 manager.ParsersManager.RegisterParser(UtmpxParser) |
OLD | NEW |