def event_history(self, columns, *, sieve=None, repres=None, quiet=None): if columns: table_data = [[COLUMN_NAMES[name] for name in columns]] else: columns = [key for key in COLUMN_NAMES.keys()] table_data = [[val for val in COLUMN_NAMES.values()]] self._events_to_show = _filter_events(self._all_events, sieve) if not self._events_to_show: print_info('No USB events found!') return if not quiet: try: number, filename = _output_choice('event history', 'history.json', 'history/') except USBRipError as e: print_critical(str(e), initial_error=e.errors['initial_error']) return if number == '1': _json_dump(self._events_to_show, 'event history', filename) return _represent_events(self._events_to_show, columns, table_data, 'USB-History-Events', repres)
def _read_log_file(filename): filtered = DefaultOrderedDict(list) if filename.endswith('.gz'): print_info('Unpacking \'{}\''.format(filename)) try: log = gzip.open(filename, 'rb') except PermissionError as e: print_warning('Permission denied: \'{}\''.format(filename), initial_error=str(e)) return filtered else: sentinel = b'' filename = filename[:-3] else: log = open(filename, 'r') sentinel = '' print_info('Reading \'{}\''.format(filename)) regex = re.compile(r'usb') for line in iter(log.readline, sentinel): if isinstance(line, bytes): line = line.decode(encoding='utf-8') if regex.search(line): filtered[line[:15]].append(line) # line[:15] == 'Mon dd hh:mm:ss' log.close() return filtered
def _filter_events(all_events, sieve=None): if not sieve: sieve = {'external': False, 'dates': [], 'number': -1} else: print_info('Filtering events') if sieve['external']: events_to_show = [ event for event in all_events if event['disconn'] is not None ] else: events_to_show = all_events if sieve['dates']: events_to_show = [ event for date in sieve['dates'] for event in events_to_show if event['conn'][:6] == date ] if not len(events_to_show): return None SIZE = len(events_to_show) if sieve['number'] == -1 or sieve['number'] >= SIZE: if sieve['number'] > SIZE: print_warning('USB action history has only {} entries instead of requested {}, ' \ 'displaying all of them...'.format(SIZE, sieve['number'])) sieve['number'] = SIZE return [events_to_show[SIZE - i] for i in range(sieve['number'], 0, -1)]
def generate_auth_json(self, output_auth): try: os_makedirs(os.path.dirname(output_auth)) except USBRipError as e: print_critical(str(e), initial_error=e.errors['initial_error']) return try: auth_json = open(output_auth, 'w') except PermissionError as e: print_critical('Permission denied: \'{}\''.format(output_auth), initial_error=str(e)) return print_info('Generating authorized device list (JSON)') auth = defaultdict(list) for event in self._all_events: for key, val in event.items(): if key in ('vid', 'pid', 'prod', 'manufact', 'serial') and \ val is not None and \ val not in auth[key]: auth[key].append(val) for key in auth.keys(): auth[key].sort() json.dump(auth, auth_json, sort_keys=True, indent=4) auth_json.close() print_info('New authorized device list: \'{}\''.format(output_auth))
def _get_raw_history(): raw_history = DefaultOrderedDict(list) print_info( 'Searching for log files: \'/var/log/syslog*\' or \'/var/log/messages*\'' ) syslog_files = sorted([ filename for filename in list_files('/var/log/') if filename.rsplit('/', 1)[1].startswith('syslog') ]) if len(syslog_files) > 0: for syslog in syslog_files: raw_history.update(_read_log_file(syslog)) else: messages_files = sorted([ filename for filename in list_files('/var/log/') if filename.rsplit('/', 1)[1].startswith('messages') ]) if len(messages_files) > 0: for messages in messages_files: raw_history.update(_read_log_file(messages)) else: raise USBRipError('None of log file types was found!') return raw_history
def _output_choice(list_name, default_filename, dirname): while True: print('[?] How would you like your {} list to be generated?\n'.format( list_name)) print(' 1. JSON-file') print(' 2. Terminal stdout') number = input('\n[>] Please enter the number of your choice: ') if number == '1': while True: filename = input('[>] Please enter the base name for the output file ' \ '(default is \'{}\'): '.format(default_filename)) if all(c in printable for c in filename) and len(filename) < 256: if not filename: filename = default_filename elif filename[-5:] != '.json': filename = filename + '.json' filename = root_dir_join(dirname + filename) try: dirname = os.path.dirname(filename) os_makedirs(dirname) except USBRipError as e: print_critical(str(e), initial_error=e.errors['initial_error']) return (None, '') else: print_info('Created \'{}\''.format(dirname)) overwrite = True if os.path.exists(filename): while True: overwrite = input( '[?] File exists. Would you like to overwrite it? [Y/n]: ' ) if len(overwrite) == 1 and overwrite in 'Yy': overwrite = True break elif len(overwrite) == 1 and overwrite in 'Nn': overwrite = False break if overwrite: return (int(number), filename) elif number == '2': return (int(number), '')
def search_violations(self, input_auth, columns, *, sieve=None, repres=None): try: auth = _process_auth_json(input_auth) except json.decoder.JSONDecodeError as e: print_critical('Failed to decode authorized device list (JSON)', initial_error=str(e)) return if columns: table_data = [[COLUMN_NAMES[name] for name in columns]] else: columns = [key for key in COLUMN_NAMES.keys()] table_data = [[val for val in COLUMN_NAMES.values()]] print_info('Searching for violations', quiet=USBEvents.QUIET) for event in self._all_events: try: if any(event[key] not in vals and event[key] is not None for key, vals in auth.items()): self._violations.append(event) except KeyError as e: print_critical( 'Invalid structure of authorized device list (JSON)', initial_error=str(e)) return self._events_to_show = _filter_events(self._violations, sieve) if not self._events_to_show: print_info('No USB violation events found!', quiet=USBEvents.QUIET) json.dump([], auth_json) auth_json.close() return if not USBEvents.QUIET and ISATTY: number, filename = _output_choice('violation', 'viol.json', 'violations/') if number is None: return elif number == 1: _json_dump(self._events_to_show, 'violation', filename) return _represent_events(self._events_to_show, columns, table_data, 'USB-Violation-Events', repres)
def search_violations(self, input_auth, *, sieve=None, repres=None, quiet=None): try: auth = _process_auth_json(input_auth) except json.decoder.JSONDecodeError as e: print_critical('Failed to decode authorized device list (JSON)', initial_error=str(e)) return print_info('Searching for violations') for event in self._all_events: try: if any(event[key] not in vals and event[key] is not None for key, vals in auth.items()): self._violations.append(event) except KeyError as e: print_critical( 'Invalid structure of authorized device list (JSON)', initial_error=str(e)) return columns = [key for key in COLUMN_NAMES.keys()] table_data = [[val for val in COLUMN_NAMES.values()]] self._events_to_show = _filter_events(self._violations, sieve) if not self._events_to_show: print_info('No USB violation events found!') return if not quiet: try: number, filename = _output_choice('violation', 'viol.json', 'violations/') except USBRipError as e: print_critical(str(e), initial_error=e.errors['initial_error']) return if number == '1': _json_dump(self._events_to_show, 'violation', filename) return _represent_events(self._events_to_show, columns, table_data, 'USB-Violation-Events', repres)
def _download_database(filename): try: os_makedirs(os.path.dirname(filename)) except USBRipError as e: print_critical(str(e), initial_error=e.errors['initial_error']) return try: usb_ids = open(filename, 'w+') except PermissionError as e: print_critical('Permission denied: \'{}\''.format(filename), initial_error=str(e)) return db, latest_ver, latest_date, error, e = _get_latest_version() if error: usb_ids.close() os.remove(filename) if error == USBIDs._INTERNET_CONNECTION_ERROR: raise USBRipError('No internet connection') elif error == USBIDs._SERVER_TIMEOUT_ERROR: raise USBRipError('Server timeout', errors={ 'errcode': error, 'initial_error': e }) elif error == USBIDs._SERVER_CONTENT_ERROR: raise USBRipError('Server content error: no version or date found', errors={ 'errcode': error, 'initial_error': e }) usb_ids.write(db) usb_ids.seek(0) print_info('Database downloaded') print('Version: {}'.format(latest_ver)) print('Date: {}'.format(latest_date)) return usb_ids
def _json_dump(events_to_show, list_name, filename): print_info('Generating {} list (JSON)'.format(list_name)) out = OrderedDict() for event in events_to_show: out[event['conn']] = OrderedDict() for key, val in sorted(event.items()): if key != 'conn': out[event['conn']][key] = val try: with open(filename, 'w') as out_json: json.dump(out, out_json, indent=4) except PermissionError as e: print_critical('Permission denied: \'{}\''.format(filename), initial_error=str(e)) return print_info('New {} list: \'{}\''.format(list_name, filename))
def _get_latest_version(): connected, error, e = _check_connection('www.google.com') if not connected: return (None, -1, -1, error, e) print_info('Getting latest version and date', quiet=USBIDs.QUIET) try: resp = requests.get('http://www.linux-usb.org/usb.ids', timeout=10) except requests.exceptions.Timeout as e: return (None, -1, -1, USBIDs._SERVER_TIMEOUT_ERROR, str(e)) soup = BeautifulSoup(resp.text, 'html.parser') db = soup.text try: latest_ver = re.search(r'^# Version:\s*(.*?$)', db, re.MULTILINE).group(1) latest_date = re.search(r'^# Date:\s*(.*?$)', db, re.MULTILINE).group(1) except AttributeError as e: return (None, -1, -1, USBIDs._SERVER_CONTENT_ERROR, str(e)) return (db, latest_ver, latest_date, 0, '')
def event_history(self, columns, *, sieve=None, repres=None): if columns: table_data = [[COLUMN_NAMES[name] for name in columns]] else: columns = [key for key in COLUMN_NAMES.keys()] table_data = [[val for val in COLUMN_NAMES.values()]] self._events_to_show = _filter_events(self._all_events, sieve) if not self._events_to_show: print_info('No USB events found!', quiet=USBEvents.QUIET) return if not USBEvents.QUIET and ISATTY: number, filename = _output_choice('event history', 'history.json', 'history/') if number is None: return elif number == 1: _json_dump(self._events_to_show, 'event history', filename) return _represent_events(self._events_to_show, columns, table_data, 'USB-History-Events', repres)
def generate_auth_json(self, output_auth, *, sieve=None): try: dirname = os.path.dirname(filename) os_makedirs(dirname) except USBRipError as e: print_critical(str(e), initial_error=e.errors['initial_error']) return else: print_info('Created \'{}\''.format(dirname)) try: auth_json = open(output_auth, 'w') except PermissionError as e: print_critical('Permission denied: \'{}\''.format(output_auth), initial_error=str(e)) return self._events_to_show = _filter_events(self._all_events, sieve) if not self._events_to_show: print_info('No USB violation events found!', quiet=USBEvents.QUIET) json.dump([], auth_json) auth_json.close() return print_info('Generating authorized device list (JSON)', quiet=USBEvents.QUIET) auth = defaultdict(list) for event in self._events_to_show: for key, val in event.items(): if key in ('vid', 'pid', 'prod', 'manufact', 'serial') and \ val is not None and \ val not in auth[key]: auth[key].append(val) for key in auth.keys(): auth[key].sort() json.dump(auth, auth_json, sort_keys=True, indent=4) auth_json.close() print_info('New authorized device list: \'{}\''.format(output_auth), quiet=USBEvents.QUIET)
def _update_database(filename): try: usb_ids = open(filename, 'r+') except PermissionError as e: print_critical('Permission denied: \'{}\''.format(filename), initial_error=str(e)) return curr_ver, curr_date = _get_current_version(usb_ids) print_info('Getting current database version') print('Version: {}'.format(curr_ver)) print('Date: {}'.format(curr_date)) print_info('Checking local database for update') db, latest_ver, latest_date, error, e = _get_latest_version() if error: if error == USBIDs._INTERNET_CONNECTION_ERROR: print_warning('No internet connection, using current version', errcode=error) elif error == USBIDs._SERVER_TIMEOUT_ERROR: print_warning('Server timeout, using current version', errcode=error, initial_error=e) elif error == USBIDs._SERVER_CONTENT_ERROR: print_warning('Server error, using current version', errcode=error, initial_error=e) return usb_ids if curr_ver != latest_ver and curr_date != latest_date: # if there's newer database version print('Updating database... ', end='') usb_ids.write(db) usb_ids.seek(0) usb_ids.truncate() print('Done\n') print('Version: {}'.format(latest_ver)) print('Date: {}'.format(latest_date)) print_info('Local database is up-to-date') return usb_ids
def _represent_events(events_to_show, columns, table_data, title, repres=None): print_info('Preparing gathered events') if not repres: repres = {'table': False, 'list': False, 'smart': True} max_len = { 'conn': 15, 'user': max(max(len(event['user']) for event in events_to_show), len('User')), 'vid': 4, 'pid': 4, 'prod': max(max(len(str(event['prod'])) for event in events_to_show), len('Product')), 'manufact': max(max(len(str(event['manufact'])) for event in events_to_show), len('Manufacturer')), 'serial': max(max(len(str(event['serial'])) for event in events_to_show), len('Serial Number')), 'port': max(max(len(event['port']) for event in events_to_show), len('Port')), 'disconn': 15 } for event in events_to_show: if 'conn' in columns: try: prev_cday except NameError: prev_cday = '' curr_cday = event['conn'][:6] if prev_cday != curr_cday: cday = ['{} {}'.format(curr_cday, BULLET * 8) ] # 8 == len(event['conn'] - event['conn'][:6] - 1) table_data.append(cday + [ SEPARATOR * max_len[name] for name in columns if name != 'conn' ]) prev_cday = curr_cday row = [] for name in columns: if event[name] is None: event[name] = ABSENCE item = event[name] if name == 'conn' and IS_COLORED: item = colored(item, 'green') elif name == 'disconn' and IS_COLORED: item = colored(item, 'red') row.append(item) table_data.append(row) if IS_COLORED: event_table = _build_single_table( USBEvents.TableClass, table_data, colored(title, 'white', attrs=['bold'])) else: event_table = _build_single_table(USBEvents.TableClass, table_data, title) # Display as table if repres['smart'] and event_table.ok or repres['table']: print_info('Representation: Table') print('\n' + event_table.table) # Display as list elif repres['smart'] and not event_table.ok or repres['list']: if not event_table.ok: print_warning( 'Terminal window is too small to display table properly') print_warning('Representation: List') max_len = max(len(str(val)) for event in events_to_show for val in event.values()) + \ len('Serial Number: ') # max length string if not max_len // 2: max_len += 1 date_sep_len = (max_len - 8) // 2 cprint('\n' + title, 'white', attrs=['bold']) prev_cday = '' for event in events_to_show: curr_cday = event['conn'][:6] if prev_cday != curr_cday: print(SEPARATOR * max_len) print('{} {} {}'.format(BULLET * date_sep_len, curr_cday, BULLET * date_sep_len)) print(SEPARATOR * max_len) else: print(SEPARATOR * max_len) prev_cday = curr_cday if IS_COLORED: print( colored('Connected: ', 'magenta', attrs=['bold']) + colored(event['conn'], 'green')) print( colored('User: '******'magenta', attrs=['bold']) + event['user']) print( colored('VID: ', 'magenta', attrs=['bold']) + event['vid']) print( colored('PID: ', 'magenta', attrs=['bold']) + event['pid']) print( colored('Product: ', 'magenta', attrs=['bold']) + str(event['prod'])) print( colored('Manufacturer: ', 'magenta', attrs=['bold']) + str(event['manufact'])) print( colored('Serial Number: ', 'magenta', attrs=['bold']) + str(event['serial'])) print( colored('Bus-Port: ', 'magenta', attrs=['bold']) + event['port']) print( colored('Disconnected: ', 'magenta', attrs=['bold']) + colored(event['disconn'], 'red')) else: print('Connected: ' + event['conn']) print('User: '******'user']) print('VID: ' + event['vid']) print('PID: ' + event['pid']) print('Product: ' + str(event['prod'])) print('Manufacturer: ' + str(event['manufact'])) print('Serial Number: ' + str(event['serial'])) print('Bus-Port: ' + event['port']) print('Disconnected: ' + event['disconn']) print(SEPARATOR * max_len)