def get_config_parser(): config_parser = ConfigParser(allow_no_value=True) config_parser.optionxform = str os.makedirs(CONFIG_FILE.rsplit('/', 1)[0], exist_ok=True) if os.path.isfile(CONFIG_FILE): config_parser.read(CONFIG_FILE, encoding='utf-8') print_info(f'Configuration loaded: "{CONFIG_FILE}"') else: print_warning('No configuration file found, creating new one...') config_parser.add_section('history') config_parser.set('history', 'password', 'r1pp3r') config_parser.add_section('violations') config_parser.set('violations', 'password', 'r1pp3r') print_info(f'New configuration file: "{CONFIG_FILE}"') with open(CONFIG_FILE, 'w', encoding='utf-8') as f: config_parser.write(f) return config_parser
def _read_log_file(filename): filtered = DefaultOrderedDict(default_factory=list) if filename.endswith('.gz'): print_info(f'Unpacking "{filename}"') try: log = gzip.open(filename, 'rb') except PermissionError as e: print_warning(f'Permission denied: "{filename}". Retry with sudo', initial_error=str(e)) return filtered else: end_of_file = b'' filename = filename[:-3] else: log = codecs.open(filename, 'r', encoding='utf-8', errors='ignore') end_of_file = '' print_info(f'Reading "{filename}"') regex = re.compile(r'usb') for line in iter(log.readline, end_of_file): if isinstance(line, bytes): line = line.decode(encoding='utf-8', errors='ignore') if regex.search(line): filtered[line[:15]].append(line) # line[:15] == 'Mon dd hh:mm:ss' log.close() return filtered
def _read_log_file(filename): filtered = [] abs_filename = os.path.abspath(filename) if abs_filename.endswith('.gz'): print_info(f'Unpacking "{abs_filename}"') try: log = gzip.open(abs_filename, 'rb') except PermissionError as e: print_warning( f'Permission denied: "{abs_filename}". Retry with sudo', initial_error=str(e)) return filtered else: end_of_file = b'' abs_filename = os.path.splitext(abs_filename) else: log = codecs.open(abs_filename, 'r', encoding='utf-8', errors='ignore') end_of_file = '' print_info(f'Reading "{abs_filename}"') regex = re.compile(r'] usb (.*?): ') for line in iter(log.readline, end_of_file): if isinstance(line, bytes): line = line.decode(encoding='utf-8', errors='ignore') if regex.search(line): date = line[:32] if date.count(':') > 2: date = ''.join( line[:32].rsplit(':', 1) ) # rreplace(':', 1) to remove the last ':' from "2019-08-09T06:15:49.655261-04:00" timestamp if there is one try: date = datetime.strptime( date, '%Y-%m-%dT%H:%M:%S.%f%z' ) # ex. 2019-08-09T06:15:49.655261-0400 except ValueError as e: raise USBRipError( f'Wrong timestamp format found in "{abs_filename}"', errors={'initial_error': str(e)}) else: date = date.strftime('%Y-%m-%d %H:%M:%S') logline = line[32:].strip() if any(pat in line for pat in ('New USB device found, ', 'Product: ', 'Manufacturer: ', 'SerialNumber: ')): filtered.append((date, 'c', logline)) elif 'disconnect' in line: filtered.append((date, 'd', logline)) log.close() return filtered
def search_ids(vid, pid, *, offline=True): if offline: print_warning('Offline mode') try: usb_ids = USBIDs.prepare_database(offline=offline) except USBRipError as e: print_critical(str(e), errcode=e.errors['errcode'], initial_error=e.errors['initial_error']) else: _search_ids_helper(usb_ids, vid, pid) usb_ids.close()
def __new__(cls, files=None): try: if files: filtered_history = [] for file in files: filtered_history.extend(_read_log_file(file)) else: print_info('Trying to run journalctl...') # child_env = os.environ.copy() # child_env['LANG'] = 'en_US.utf-8' # journalctl_out = check_output(['journalctl'], env=child_env).decode('utf-8') try: journalctl_out = check_output([ 'journalctl', '-o', 'short-iso-precise' ]).decode('utf-8') except Exception as e: print_warning(f'Failed to run journalctl: {str(e)}') filtered_history = _get_filtered_history() else: if '-- Logs begin at' in journalctl_out: print_info('Successfully ran journalctl') filtered_history = _read_log_file( None, log=StringIO(journalctl_out), total=journalctl_out.count('\n') ) else: print_warning(f'An error occurred when running journalctl: {journalctl_out}') filtered_history = _get_filtered_history() except USBRipError as e: print_critical(str(e), initial_error=e.errors['initial_error']) return None all_events = _parse_history(filtered_history) instance = super().__new__(cls) instance._all_events = all_events # self._all_events instance._violations = [] # self._violations instance._events_to_show = None # self._events_to_show return instance
def prepare_database(*, offline=True): filename = f'{os.path.abspath(str(Path.home()))}/.config/usbrip/usb.ids' file_exists = os.path.isfile(filename) if file_exists and offline: usb_ids = open(filename, 'r', encoding='utf-8') elif file_exists and not offline: usb_ids = _update_database(filename) elif not file_exists and not offline: print_warning('No local database found, trying to download') usb_ids = _download_database(filename) elif not file_exists and offline: raise USBRipError('No local database found') return usb_ids
def _shred(filename): cmd = [ 'shred', '-z', '-u', '-n', '7', filename ] try: out = subprocess.check_output(cmd).decode('utf-8') except subprocess.CalledProcessError as e: print_warning(f'Failed to shred {filename}, using standard rm instead: {str(e)}') os.remove(filename)
def prepare_database(*, offline=True): filename = root_dir_join('usb_ids/usb.ids') file_exists = os.path.isfile(filename) if file_exists and offline: usb_ids = open(filename, 'r', encoding='utf-8') elif file_exists and not offline: usb_ids = _update_database(filename) elif not file_exists and not offline: print_warning('No local database found, trying to download') usb_ids = _download_database(filename) elif not file_exists and offline: raise USBRipError('No local database found') return usb_ids
def _filter_events(all_events, sieve): if sieve is None: sieve = {'external': False, 'number': -1, 'dates': [], 'fields': {}} if sieve != {'external': False, 'number': -1, 'dates': [], 'fields': {}}: print_info('Filtering events') events_to_show = all_events if sieve['fields']: events_to_show = [] for key, vals in sieve['fields'].items(): events_to_show += [ event for event in all_events for val in vals if event[key] == val ] if sieve['external']: events_to_show = [ event for event in all_events if event['disconn'] is not None ] 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 events_to_show: return [] SIZE = len(events_to_show) if sieve['number'] == -1 or sieve['number'] >= SIZE: if sieve['number'] > SIZE: print_warning( f'USB action history has only {SIZE} entries instead of requested {sieve["number"]}, ' f'displaying all of them...') sieve['number'] = SIZE return [events_to_show[SIZE - i] for i in range(sieve['number'], 0, -1)]
def _update_database(filename): try: usb_ids = open(filename, 'r+', encoding='utf-8') except PermissionError as e: raise USBRipError( f'Permission denied: "{filename}"', errors={'initial_error': str(e)} ) print_info('Getting current database version') curr_ver, curr_date = _get_current_version(usb_ids) print(f'Version: {curr_ver}') print(f'Date: {curr_date}') print_info('Checking local database for update') db, latest_ver, latest_date, errcode, e = _get_latest_version() if errcode: if errcode == USBIDs._INTERNET_CONNECTION_ERROR: print_warning( 'No internet connection, using current version', errcode=errcode ) elif errcode == USBIDs._SERVER_TIMEOUT_ERROR: print_warning( 'Server timeout, using current version', errcode=errcode, initial_error=e ) elif errcode == USBIDs._SERVER_CONTENT_ERROR: print_warning( 'Server error, using current version', errcode=errcode, 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.truncate() usb_ids.seek(0) print('Done\n') print(f'Version: {latest_ver}') print(f'Date: {latest_date}') print_info('Local database is up-to-date') return usb_ids
def _represent_events(events_to_show, columns, table_data, title, repres): print_info('Preparing gathered events') if repres is None: repres = {'table': False, 'list': False, 'smart': True} max_len = { 'conn': 19, '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': 19 } prev_cday = '' for event in events_to_show: if 'conn' in columns: curr_cday = event['conn'][:10] if prev_cday != curr_cday: cday = [ f'{curr_cday} {BULLET * (len(event["conn"])-len(curr_cday)-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 cfg.ISATTY: item = colored(item, 'green') elif name == 'disconn' and cfg.ISATTY: item = colored(item, 'red') row.append(item) table_data.append(row) event_table = _build_single_table(USBEvents.TableClass, table_data, colored(title, 'white', attrs=['bold'])) # Display as table if cfg.ISATTY and (repres['smart'] and event_table.ok or repres['table']): print_info('Representation: Table') print('\n' + event_table.table) # Display as list elif not cfg.ISATTY or (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') else: print_info('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 if cfg.ISATTY: cprint('\n' + title, 'white', attrs=['bold']) else: print('\n' + title) print(SEPARATOR * max_len) for event in events_to_show: if cfg.ISATTY: 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)
def _filter_events(all_events, sieve): # sieve = { # 'external': False, # 'dates': [], # 'fields': {}, # 'number': -1 # } if sieve is None: return all_events else: print_info('Filtering events') events_by_external = [] if sieve['external']: for event in all_events: if event['disconn'] is not None: events_by_external.append(event) continue else: events_by_external = all_events events_by_date = [] if sieve['dates']: for event in all_events: for date in sieve['dates']: if event['conn'].startswith(date): events_by_date.append(event) break continue else: events_by_date = all_events event_intersection = intersect_event_sets(events_by_external, events_by_date) events_to_show = [] if sieve['fields']: for event in event_intersection: for key, vals in sieve['fields'].items(): if any(event[key] == val for val in vals): events_to_show.append(event) break else: events_to_show = event_intersection if not events_to_show: return [] SIZE = len(events_to_show) if sieve['number'] <= -1 or sieve['number'] > SIZE: if sieve['number'] < -1: print_warning( f'usbrip can\'t handle dark matter \"--number={sieve["number"]}\", so it will show ' f'all {SIZE} USB history entries available') elif sieve['number'] > SIZE: print_warning( f'USB history has only {SIZE} entries instead of requested {sieve["number"]}, ' f'displaying all of them...') sieve['number'] = SIZE return [ events_to_show[SIZE - i] for i in range(sieve['number'], 0, -1) ]
def _read_log_file(filename, log=None): filtered = [] if log is None: abs_filename = os.path.abspath(filename) if abs_filename.endswith('.gz'): print_info(f'Unpacking "{abs_filename}"') try: log = gzip.open(abs_filename, 'rb') except PermissionError as e: print_warning( f'Permission denied: "{abs_filename}". Retry with sudo', initial_error=str(e)) return filtered else: end_of_file = b'' abs_filename = os.path.splitext(abs_filename) else: log = codecs.open(abs_filename, 'r', encoding='utf-8', errors='ignore') end_of_file = '' print_info(f'Reading "{abs_filename}"') else: abs_filename = 'journalctl output' end_of_file = '' print_info(f'Reading journalctl output') regex = re.compile(r'(?:]|:) usb (.*?): ') for line in tqdm(iter(log.readline, end_of_file), unit='line'): if isinstance(line, bytes): line = line.decode('utf-8', errors='ignore') if regex.search(line): # Case 1 -- Modified Timestamp ("%Y-%m-%dT%H:%M:%S.%f%z") date = line[:32].strip() if date.count(':') == 3: date = ''.join( line[:32].rsplit(':', 1) ) # rreplace(':', '', 1) to remove the last ':' from "2019-08-09T06:15:49.655261-04:00" timestamp if there is one try: date = datetime.strptime( date, '%Y-%m-%dT%H:%M:%S.%f%z' ) # ex. "2019-08-09T06:15:49.655261-0400" except ValueError: # Case 2 -- Non-Modified Timestamp ("%b %d %H:%M:%S") date = line[:15].strip() if ' ' in date: date = date.replace(' ', ' 0', 1) # pad day of the week with zero try: date = datetime.strptime( date, '%b %d %H:%M:%S') # ex. "Mar 18 13:56:07" except ValueError as e: raise USBRipError( f'Wrong timestamp format found in "{abs_filename}"', errors={'initial_error': str(e)}) else: date = date.strftime('????-%m-%d %H:%M:%S') logline = line[15:].strip() else: date = date.strftime('%Y-%m-%d %H:%M:%S') logline = line[32:].strip() if any(pat in line for pat in ('New USB device found, ', 'Product: ', 'Manufacturer: ', 'SerialNumber: ')): filtered.append((date, 'c', logline)) elif 'disconnect' in line: filtered.append((date, 'd', logline)) log.close() return filtered
def create_storage(storage_type, *, password=None, input_auth=None, attributes=None, compression_level='5', indent=4, sieve=None): if storage_type == 'history': events_to_show = _get_history_events(sieve) elif storage_type == 'violations': try: events_to_show = _get_violation_events(sieve, input_auth, attributes, indent) except USBRipError as e: print_critical(str(e), initial_error=e.errors['initial_error']) return 1 if events_to_show is None: return 1 if events_to_show: min_date, max_date = _get_dates(events_to_show) json_file = '{}/{}-{}.json'.format(USBStorage._STORAGE_BASE, min_date, max_date) else: json_file = '{}/{}.json'.format(USBStorage._STORAGE_BASE, datetime.now().strftime('%m%d')) try: _dump_events(events_to_show, storage_type, json_file, indent) except USBRipError as e: print_critical(str(e), initial_error=e.errors['initial_error']) return 1 if password is None: print_warning('No password provided, generating random one') password = _gen_random_password(12) storage_full_path = '{}/{}.7z'.format(USBStorage._STORAGE_BASE, storage_type) if os.path.exists(storage_full_path): os.remove(storage_full_path) try: out = _7zip_pack(storage_full_path, json_file, password, compression_level) except USBRipError as e: os.remove(json_file) print_critical(str(e), errcode=e.errors['errcode'], initial_error=e.errors['initial_error']) return 1 if 'Everything is Ok' in out: print_info('New {} storage: \'{}\''.format(storage_type, storage_full_path)) print_secret('Your password is', secret=password) os.remove(json_file) else: print_critical('Undefined behaviour while creating storage', initial_error=out)