tfc-mirror/src/transmitter/commands.py

692 lines
33 KiB
Python
Executable File

#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
"""
TFC - Onion-routed, endpoint secure messaging system
Copyright (C) 2013-2019 Markus Ottela
This file is part of TFC.
TFC is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
TFC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TFC. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import readline
import struct
import textwrap
import time
import typing
from multiprocessing import Queue
from typing import Any, Dict, List, Tuple, Union
from src.common.db_logs import access_logs, change_log_db_key, remove_logs
from src.common.encoding import b58decode, b58encode, bool_to_bytes, int_to_bytes, onion_address_to_pub_key
from src.common.exceptions import FunctionReturn
from src.common.input import yes
from src.common.misc import ensure_dir, get_terminal_width, validate_onion_addr
from src.common.output import clear_screen, m_print, phase, print_on_previous_line
from src.common.statics import *
from src.transmitter.commands_g import process_group_command
from src.transmitter.contact import add_new_contact, change_nick, contact_setting, remove_contact
from src.transmitter.key_exchanges import export_onion_service_data, new_local_key, rxp_load_psk, verify_fingerprints
from src.transmitter.packet import cancel_packet, queue_command, queue_message, queue_to_nc
from src.transmitter.user_input import UserInput
from src.transmitter.windows import select_window
if typing.TYPE_CHECKING:
from src.common.db_contacts import ContactList
from src.common.db_groups import GroupList
from src.common.db_masterkey import MasterKey
from src.common.db_onion import OnionService
from src.common.db_settings import Settings
from src.common.gateway import Gateway
from src.transmitter.windows import TxWindow
QueueDict = Dict[bytes, Queue]
def process_command(user_input: 'UserInput',
window: 'TxWindow',
contact_list: 'ContactList',
group_list: 'GroupList',
settings: 'Settings',
queues: 'QueueDict',
master_key: 'MasterKey',
onion_service: 'OnionService',
gateway: 'Gateway'
) -> None:
"""\
Select function based on the first keyword of the
issued command, and pass relevant parameters to it.
"""
# Keyword Function to run ( Parameters )
# -----------------------------------------------------------------------------------------------------------------------------------------
d = {'about': (print_about, ),
'add': (add_new_contact, contact_list, group_list, settings, queues, onion_service ),
'cf': (cancel_packet, user_input, window, settings, queues ),
'cm': (cancel_packet, user_input, window, settings, queues ),
'clear': (clear_screens, user_input, window, settings, queues ),
'cmd': (rxp_show_sys_win, user_input, window, settings, queues ),
'connect': (send_onion_service_key, contact_list, settings, onion_service, gateway),
'exit': (exit_tfc, settings, queues, gateway),
'export': (log_command, user_input, window, contact_list, group_list, settings, queues, master_key ),
'fw': (rxp_show_sys_win, user_input, window, settings, queues ),
'group': (process_group_command, user_input, contact_list, group_list, settings, queues, master_key ),
'help': (print_help, settings ),
'history': (log_command, user_input, window, contact_list, group_list, settings, queues, master_key ),
'localkey': (new_local_key, contact_list, settings, queues, ),
'logging': (contact_setting, user_input, window, contact_list, group_list, settings, queues ),
'msg': (select_window, user_input, window, settings, queues, onion_service, gateway),
'names': (print_recipients, contact_list, group_list, ),
'nick': (change_nick, user_input, window, contact_list, group_list, settings, queues ),
'notify': (contact_setting, user_input, window, contact_list, group_list, settings, queues ),
'passwd': (change_master_key, user_input, contact_list, group_list, settings, queues, master_key, onion_service ),
'psk': (rxp_load_psk, window, contact_list, settings, queues ),
'reset': (clear_screens, user_input, window, settings, queues ),
'rm': (remove_contact, user_input, window, contact_list, group_list, settings, queues, master_key ),
'rmlogs': (remove_log, user_input, contact_list, group_list, settings, queues, master_key ),
'set': (change_setting, user_input, window, contact_list, group_list, settings, queues, gateway),
'settings': (print_settings, settings, gateway),
'store': (contact_setting, user_input, window, contact_list, group_list, settings, queues ),
'unread': (rxp_display_unread, settings, queues ),
'verify': (verify, window, contact_list ),
'whisper': (whisper, user_input, window, settings, queues ),
'whois': (whois, user_input, contact_list, group_list ),
'wipe': (wipe, settings, queues, gateway)
} # type: Dict[str, Any]
try:
cmd_key = user_input.plaintext.split()[0]
except (IndexError, UnboundLocalError):
raise FunctionReturn("Error: Invalid command.", head_clear=True)
try:
from_dict = d[cmd_key]
except KeyError:
raise FunctionReturn(f"Error: Invalid command '{cmd_key}'.", head_clear=True)
func = from_dict[0]
parameters = from_dict[1:]
func(*parameters)
def print_about() -> None:
"""Print URLs that direct to TFC's project site and documentation."""
clear_screen()
print(f"\n Tinfoil Chat {VERSION}\n\n"
" Website: https://github.com/maqp/tfc/\n"
" Wikipage: https://github.com/maqp/tfc/wiki\n")
def clear_screens(user_input: 'UserInput',
window: 'TxWindow',
settings: 'Settings',
queues: 'QueueDict'
) -> None:
"""Clear/reset screen of Source, Destination, and Networked Computer.
Only send an unencrypted command to Networked Computer if traffic
masking is disabled.
With clear command, sending only the command header is enough.
However, as reset command removes the ephemeral message log on
Receiver Program, Transmitter Program must define the window to
reset (in case, e.g., previous window selection command packet
dropped, and active window state is inconsistent between the
TCB programs).
"""
clear = user_input.plaintext.split()[0] == CLEAR
command = CLEAR_SCREEN if clear else RESET_SCREEN + window.uid
queue_command(command, settings, queues)
clear_screen()
if not settings.traffic_masking:
pt_cmd = UNENCRYPTED_SCREEN_CLEAR if clear else UNENCRYPTED_SCREEN_RESET
packet = UNENCRYPTED_DATAGRAM_HEADER + pt_cmd
queue_to_nc(packet, queues[RELAY_PACKET_QUEUE])
if not clear:
readline.clear_history()
os.system(RESET)
def rxp_show_sys_win(user_input: 'UserInput',
window: 'TxWindow',
settings: 'Settings',
queues: 'QueueDict',
) -> None:
"""\
Display a system window on Receiver Program until the user presses
Enter.
Receiver Program has a dedicated window, WIN_UID_LOCAL, for system
messages that shows information about received commands, status
messages etc.
Receiver Program also has another window, WIN_UID_FILE, that shows
progress of file transmission from contacts that have traffic
masking enabled.
"""
cmd = user_input.plaintext.split()[0]
win_uid = dict(cmd=WIN_UID_LOCAL, fw=WIN_UID_FILE)[cmd]
command = WIN_SELECT + win_uid
queue_command(command, settings, queues)
try:
m_print(f"<Enter> returns Receiver to {window.name}'s window", manual_proceed=True, box=True)
except (EOFError, KeyboardInterrupt):
pass
print_on_previous_line(reps=4, flush=True)
command = WIN_SELECT + window.uid
queue_command(command, settings, queues)
def exit_tfc(settings: 'Settings',
queues: 'QueueDict',
gateway: 'Gateway'
) -> None:
"""Exit TFC on all three computers.
To exit TFC as fast as possible, this function starts by clearing
all command queues before sending the exit command to Receiver
Program. It then sends an unencrypted exit command to Relay Program
on Networked Computer. As the `sender_loop` process loads the
unencrypted exit command from queue, it detects the user's
intention, and after outputting the packet, sends the EXIT signal to
Transmitter Program's main() method that's running the
`monitor_processes` loop. Upon receiving the EXIT signal,
`monitor_processes` kills all Transmitter Program's processes and
exits the program.
During local testing, this function adds some delays to prevent TFC
programs from dying when sockets disconnect.
"""
for q in [COMMAND_PACKET_QUEUE, RELAY_PACKET_QUEUE]:
while queues[q].qsize() > 0:
queues[q].get()
queue_command(EXIT_PROGRAM, settings, queues)
if not settings.traffic_masking:
if settings.local_testing_mode:
time.sleep(LOCAL_TESTING_PACKET_DELAY)
time.sleep(gateway.settings.data_diode_sockets * 1.5)
else:
time.sleep(gateway.settings.race_condition_delay)
relay_command = UNENCRYPTED_DATAGRAM_HEADER + UNENCRYPTED_EXIT_COMMAND
queue_to_nc(relay_command, queues[RELAY_PACKET_QUEUE])
def log_command(user_input: 'UserInput',
window: 'TxWindow',
contact_list: 'ContactList',
group_list: 'GroupList',
settings: 'Settings',
queues: 'QueueDict',
master_key: 'MasterKey'
) -> None:
"""Display message logs or export them to plaintext file on TCBs.
Transmitter Program processes sent, Receiver Program sent and
received, messages of all participants in the active window.
"""
cmd = user_input.plaintext.split()[0]
export, header = dict(export =(True, LOG_EXPORT),
history=(False, LOG_DISPLAY))[cmd]
try:
msg_to_load = int(user_input.plaintext.split()[1])
except ValueError:
raise FunctionReturn("Error: Invalid number of messages.", head_clear=True)
except IndexError:
msg_to_load = 0
try:
command = header + int_to_bytes(msg_to_load) + window.uid
except struct.error:
raise FunctionReturn("Error: Invalid number of messages.", head_clear=True)
if export:
if not yes(f"Export logs for '{window.name}' in plaintext?", abort=False):
raise FunctionReturn("Log file export aborted.", tail_clear=True, head=0, delay=1)
queue_command(command, settings, queues)
access_logs(window, contact_list, group_list, settings, master_key, msg_to_load, export)
if export:
raise FunctionReturn(f"Exported log file of {window.type} '{window.name}'.", head_clear=True)
def send_onion_service_key(contact_list: 'ContactList',
settings: 'Settings',
onion_service: 'OnionService',
gateway: 'Gateway'
) -> None:
"""Resend Onion Service key to Relay Program on Networked Computer.
This command is used in cases where Relay Program had to be
restarted for some reason (e.g. due to system updates).
"""
try:
if settings.traffic_masking:
m_print(["Warning!",
"Exporting Onion Service data to Networked Computer ",
"during traffic masking can reveal to an adversary ",
"TFC is being used at the moment. You should only do ",
"this if you've had to restart the Relay Program."], bold=True, head=1, tail=1)
if not yes("Proceed with the Onion Service data export?", abort=False):
raise FunctionReturn("Onion Service data export canceled.", tail_clear=True, delay=1, head=0)
export_onion_service_data(contact_list, settings, onion_service, gateway)
except (EOFError, KeyboardInterrupt):
raise FunctionReturn("Onion Service data export canceled.", tail_clear=True, delay=1, head=2)
def print_help(settings: 'Settings') -> None:
"""Print the list of commands."""
def help_printer(tuple_list: List[Union[Tuple[str, str, bool]]]) -> None:
"""Print list of commands and their descriptions.
Style in which commands are printed depends on terminal width.
Depending on whether traffic masking is enabled, some commands
are either displayed or hidden.
"""
len_longest_command = max(len(t[0]) for t in tuple_list) + 1 # Add one for spacing
wrapper = textwrap.TextWrapper(width=max(1, terminal_width - len_longest_command))
for help_cmd, description, display in tuple_list:
if not display:
continue
desc_lines = wrapper.fill(description).split('\n')
desc_indent = (len_longest_command - len(help_cmd)) * ' '
print(help_cmd + desc_indent + desc_lines[0])
# Print wrapped description lines with indent
if len(desc_lines) > 1:
for line in desc_lines[1:]:
print(len_longest_command * ' ' + line)
print('')
# ------------------------------------------------------------------------------------------------------------------
y_tm = settings.traffic_masking
n_tm = not settings.traffic_masking
common_commands = [("/about", "Show links to project resources", True),
("/add", "Add new contact", n_tm),
("/cf", "Cancel file transmission to active contact/group", y_tm),
("/cm", "Cancel message transmission to active contact/group", True),
("/clear, ' '", "Clear TFC screens", True),
("/cmd, '//'", "Display command window on Receiver", True),
("/connect", "Resend Onion Service data to Relay", True),
("/exit", "Exit TFC on all three computers", True),
("/export (n)", "Export (n) messages from recipient's log file", True),
("/file", "Send file to active contact/group", True),
("/fw", "Display file reception window on Receiver", y_tm),
("/help", "Display this list of commands", True),
("/history (n)", "Print (n) messages from recipient's log file", True),
("/localkey", "Generate new local key pair", n_tm),
("/logging {on,off}(' all')", "Change message log setting (for all contacts)", True),
("/msg {A,N,G}", "Change recipient to Account, Nick, or Group", n_tm),
("/names", "List contacts and groups", True),
("/nick N", "Change nickname of active recipient/group to N", True),
("/notify {on,off} (' all')", "Change notification settings (for all contacts)", True),
("/passwd {tx,rx}", "Change master password on target system", n_tm),
("/psk", "Open PSK import dialog on Receiver", n_tm),
("/reset", "Reset ephemeral session log for active window", True),
("/rm {A,N}", "Remove contact specified by account A or nick N", n_tm),
("/rmlogs {A,N}", "Remove log entries for account A or nick N", True),
("/set S V", "Change setting S to value V", True),
("/settings", "List setting names, values and descriptions", True),
("/store {on,off} (' all')", "Change file reception (for all contacts)", True),
("/unread, ' '", "List windows with unread messages on Receiver", True),
("/verify", "Verify fingerprints with active contact", True),
("/whisper M", "Send message M, asking it not to be logged", True),
("/whois {A,N}", "Check which A corresponds to N or vice versa", True),
("/wipe", "Wipe all TFC user data and power off systems", True),
("Shift + PgUp/PgDn", "Scroll terminal up/down", True)]
group_commands = [("/group create G A₁..Aₙ", "Create group G and add accounts A₁..Aₙ", n_tm),
("/group join ID G A₁..Aₙ", "Join group ID, call it G and add accounts A₁..Aₙ", n_tm),
("/group add G A₁..Aₙ", "Add accounts A₁..Aₙ to group G", n_tm),
("/group rm G A₁..Aₙ", "Remove accounts A₁..Aₙ from group G", n_tm),
("/group rm G", "Remove group G", n_tm)]
terminal_width = get_terminal_width()
clear_screen()
print(textwrap.fill("List of commands:", width=terminal_width))
print('')
help_printer(common_commands)
print(terminal_width * '')
if settings.traffic_masking:
print('')
else:
print(textwrap.fill("Group management:", width=terminal_width))
print('')
help_printer(group_commands)
print(terminal_width * '' + '\n')
def print_recipients(contact_list: 'ContactList', group_list: 'GroupList') -> None:
"""Print the list of contacts and groups."""
contact_list.print_contacts()
group_list.print_groups()
def change_master_key(user_input: 'UserInput',
contact_list: 'ContactList',
group_list: 'GroupList',
settings: 'Settings',
queues: 'QueueDict',
master_key: 'MasterKey',
onion_service: 'OnionService'
) -> None:
"""Change the master key on Transmitter/Receiver Program."""
try:
if settings.traffic_masking:
raise FunctionReturn("Error: Command is disabled during traffic masking.", head_clear=True)
try:
device = user_input.plaintext.split()[1].lower()
except IndexError:
raise FunctionReturn(f"Error: No target-system ('{TX}' or '{RX}') specified.", head_clear=True)
if device not in [TX, RX]:
raise FunctionReturn(f"Error: Invalid target system '{device}'.", head_clear=True)
if device == RX:
queue_command(CH_MASTER_KEY, settings, queues)
return None
old_master_key = master_key.master_key[:]
new_master_key = master_key.master_key = master_key.new_master_key()
phase("Re-encrypting databases")
queues[KEY_MANAGEMENT_QUEUE].put((KDB_CHANGE_MASTER_KEY_HEADER, master_key))
ensure_dir(DIR_USER_DATA)
if os.path.isfile(f'{DIR_USER_DATA}{settings.software_operation}_logs'):
change_log_db_key(old_master_key, new_master_key, settings)
contact_list.store_contacts()
group_list.store_groups()
settings.store_settings()
onion_service.store_onion_service_private_key()
phase(DONE)
m_print("Master key successfully changed.", bold=True, tail_clear=True, delay=1, head=1)
except (EOFError, KeyboardInterrupt):
raise FunctionReturn("Password change aborted.", tail_clear=True, delay=1, head=2)
def remove_log(user_input: 'UserInput',
contact_list: 'ContactList',
group_list: 'GroupList',
settings: 'Settings',
queues: 'QueueDict',
master_key: 'MasterKey'
) -> None:
"""Remove log entries for contact or group."""
try:
selection = user_input.plaintext.split()[1]
except IndexError:
raise FunctionReturn("Error: No contact/group specified.", head_clear=True)
if not yes(f"Remove logs for {selection}?", abort=False, head=1):
raise FunctionReturn("Log file removal aborted.", tail_clear=True, delay=1, head=0)
# Determine selector (group ID or Onion Service public key) from command parameters
if selection in contact_list.contact_selectors():
selector = contact_list.get_contact_by_address_or_nick(selection).onion_pub_key
elif selection in group_list.get_list_of_group_names():
selector = group_list.get_group(selection).group_id
elif len(selection) == ONION_ADDRESS_LENGTH:
if validate_onion_addr(selection):
raise FunctionReturn("Error: Invalid account.", head_clear=True)
selector = onion_address_to_pub_key(selection)
elif len(selection) == GROUP_ID_ENC_LENGTH:
try:
selector = b58decode(selection)
except ValueError:
raise FunctionReturn("Error: Invalid group ID.", head_clear=True)
else:
raise FunctionReturn("Error: Unknown selector.", head_clear=True)
# Remove logs that match the selector
command = LOG_REMOVE + selector
queue_command(command, settings, queues)
remove_logs(contact_list, group_list, settings, master_key, selector)
def change_setting(user_input: 'UserInput',
window: 'TxWindow',
contact_list: 'ContactList',
group_list: 'GroupList',
settings: 'Settings',
queues: 'QueueDict',
gateway: 'Gateway'
) -> None:
"""Change setting on Transmitter and Receiver Program."""
# Validate the KV-pair
try:
setting = user_input.plaintext.split()[1]
except IndexError:
raise FunctionReturn("Error: No setting specified.", head_clear=True)
if setting not in (settings.key_list + gateway.settings.key_list):
raise FunctionReturn(f"Error: Invalid setting '{setting}'.", head_clear=True)
try:
value = user_input.plaintext.split()[2]
except IndexError:
raise FunctionReturn("Error: No value for setting specified.", head_clear=True)
# Check if the setting can be changed
relay_settings = dict(serial_error_correction=UNENCRYPTED_EC_RATIO,
serial_baudrate =UNENCRYPTED_BAUDRATE,
allow_contact_requests =UNENCRYPTED_MANAGE_CONTACT_REQ)
if settings.traffic_masking and (setting in relay_settings or setting == 'max_number_of_contacts'):
raise FunctionReturn("Error: Can't change this setting during traffic masking.", head_clear=True)
if setting in ['use_serial_usb_adapter', 'built_in_serial_interface']:
raise FunctionReturn("Error: Serial interface setting can only be changed manually.", head_clear=True)
# Change the setting
if setting in gateway.settings.key_list:
gateway.settings.change_setting(setting, value)
else:
settings.change_setting(setting, value, contact_list, group_list)
receiver_command = CH_SETTING + setting.encode() + US_BYTE + value.encode()
queue_command(receiver_command, settings, queues)
if setting in relay_settings:
if setting == 'allow_contact_requests':
value = bool_to_bytes(settings.allow_contact_requests).decode()
relay_command = UNENCRYPTED_DATAGRAM_HEADER + relay_settings[setting] + value.encode()
queue_to_nc(relay_command, queues[RELAY_PACKET_QUEUE])
# Propagate the effects of the setting
if setting == 'max_number_of_contacts':
contact_list.store_contacts()
queues[KEY_MANAGEMENT_QUEUE].put((KDB_UPDATE_SIZE_HEADER, settings))
if setting in ['max_number_of_group_members', 'max_number_of_groups']:
group_list.store_groups()
if setting == 'traffic_masking':
queues[SENDER_MODE_QUEUE].put(settings)
queues[TRAFFIC_MASKING_QUEUE].put(settings.traffic_masking)
window.deselect()
if setting == 'log_file_masking':
queues[LOGFILE_MASKING_QUEUE].put(settings.log_file_masking)
def print_settings(settings: 'Settings',
gateway: 'Gateway') -> None:
"""Print settings and gateway settings."""
settings.print_settings()
gateway.settings.print_settings()
def rxp_display_unread(settings: 'Settings', queues: 'QueueDict') -> None:
"""\
Display the list of windows that contain unread messages on Receiver
Program.
"""
queue_command(WIN_ACTIVITY, settings, queues)
def verify(window: 'TxWindow', contact_list: 'ContactList') -> None:
"""Verify fingerprints with contact."""
if window.type == WIN_TYPE_GROUP or window.contact is None:
raise FunctionReturn("Error: A group is selected.", head_clear=True)
if window.contact.uses_psk():
raise FunctionReturn("Pre-shared keys have no fingerprints.", head_clear=True)
try:
verified = verify_fingerprints(window.contact.tx_fingerprint,
window.contact.rx_fingerprint)
except (EOFError, KeyboardInterrupt):
raise FunctionReturn("Fingerprint verification aborted.", delay=1, head=2, tail_clear=True)
status_hr, status = {True: ("Verified", KEX_STATUS_VERIFIED),
False: ("Unverified", KEX_STATUS_UNVERIFIED)}[verified]
window.contact.kex_status = status
contact_list.store_contacts()
m_print(f"Marked fingerprints with {window.name} as '{status_hr}'.", bold=True, tail_clear=True, delay=1, tail=1)
def whisper(user_input: 'UserInput',
window: 'TxWindow',
settings: 'Settings',
queues: 'QueueDict',
) -> None:
"""\
Send a message to the contact that overrides their enabled logging
setting for that message.
The functionality of this feature is impossible to enforce, but if
the recipient can be trusted and they do not modify their client,
this feature can be used to send the message off-the-record.
"""
try:
message = user_input.plaintext.strip().split(' ', 1)[1]
except IndexError:
raise FunctionReturn("Error: No whisper message specified.", head_clear=True)
queue_message(user_input=UserInput(message, MESSAGE),
window=window,
settings=settings,
queues=queues,
whisper=True,
log_as_ph=True)
def whois(user_input: 'UserInput',
contact_list: 'ContactList',
group_list: 'GroupList'
) -> None:
"""Do a lookup for a contact or group selector."""
try:
selector = user_input.plaintext.split()[1]
except IndexError:
raise FunctionReturn("Error: No account or nick specified.", head_clear=True)
# Contacts
if selector in contact_list.get_list_of_addresses():
m_print([f"Nick of '{selector}' is ",
f"{contact_list.get_contact_by_address_or_nick(selector).nick}"], bold=True)
elif selector in contact_list.get_list_of_nicks():
m_print([f"Account of '{selector}' is",
f"{contact_list.get_contact_by_address_or_nick(selector).onion_address}"], bold=True)
# Groups
elif selector in group_list.get_list_of_group_names():
m_print([f"Group ID of group '{selector}' is",
f"{b58encode(group_list.get_group(selector).group_id)}"], bold=True)
elif selector in group_list.get_list_of_hr_group_ids():
m_print([f"Name of group with ID '{selector}' is",
f"{group_list.get_group_by_id(b58decode(selector)).name}"], bold=True)
else:
raise FunctionReturn("Error: Unknown selector.", head_clear=True)
def wipe(settings: 'Settings',
queues: 'QueueDict',
gateway: 'Gateway'
) -> None:
"""\
Reset terminals, wipe all TFC user data from Source, Networked, and
Destination Computer, and power all three systems off.
The purpose of the wipe command is to provide additional protection
against physical attackers, e.g. in situation where a dissident gets
a knock on their door. By overwriting and deleting user data the
program prevents access to encrypted databases. Additional security
should be sought with full disk encryption (FDE).
Unfortunately, no effective tool for overwriting RAM currently exists.
However, as long as Source and Destination Computers use FDE and
DDR3 memory, recovery of sensitive data becomes impossible very fast:
https://www1.cs.fau.de/filepool/projects/coldboot/fares_coldboot.pdf
"""
if not yes("Wipe all user data and power off systems?", abort=False):
raise FunctionReturn("Wipe command aborted.", head_clear=True)
clear_screen()
for q in [COMMAND_PACKET_QUEUE, RELAY_PACKET_QUEUE]:
while queues[q].qsize() != 0:
queues[q].get()
queue_command(WIPE_USR_DATA, settings, queues)
if not settings.traffic_masking:
if settings.local_testing_mode:
time.sleep(0.8)
time.sleep(gateway.settings.data_diode_sockets * 2.2)
else:
time.sleep(gateway.settings.race_condition_delay)
relay_command = UNENCRYPTED_DATAGRAM_HEADER + UNENCRYPTED_WIPE_COMMAND
queue_to_nc(relay_command, queues[RELAY_PACKET_QUEUE])
os.system(RESET)