2021-12-29 18:50:03 +00:00
|
|
|
#!/usr/bin/python3
|
|
|
|
import time
|
|
|
|
import os
|
|
|
|
import subprocess
|
|
|
|
import logging
|
|
|
|
import json
|
|
|
|
import atexit
|
|
|
|
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
|
|
|
import pyudev
|
|
|
|
from systemd import journal
|
|
|
|
from threading import Thread
|
2022-02-25 18:54:42 +00:00
|
|
|
from utilities import *
|
|
|
|
from drive_info import *
|
2021-12-29 18:50:03 +00:00
|
|
|
|
|
|
|
log = logging.getLogger('mynode')
|
|
|
|
log.addHandler(journal.JournaldLogHandler())
|
|
|
|
log.setLevel(logging.INFO)
|
2022-02-25 18:54:42 +00:00
|
|
|
set_logger(log)
|
2021-12-29 18:50:03 +00:00
|
|
|
|
|
|
|
################################
|
|
|
|
## USB Device Cache
|
|
|
|
################################
|
|
|
|
usb_devices = []
|
|
|
|
|
|
|
|
def reset_usb_devices():
|
|
|
|
global usb_devices
|
|
|
|
for d in usb_devices:
|
|
|
|
d.stop()
|
|
|
|
usb_devices = []
|
|
|
|
write_usb_devices_json()
|
|
|
|
|
|
|
|
def add_usb_device(usb_device):
|
|
|
|
global usb_devices
|
|
|
|
usb_devices.append(usb_device)
|
|
|
|
write_usb_devices_json()
|
|
|
|
|
|
|
|
def remove_usb_device(usb_device):
|
|
|
|
global usb_devices
|
|
|
|
new_devices = []
|
|
|
|
for d in usb_devices:
|
|
|
|
if d.id != usb_device.id:
|
|
|
|
new_devices.append(d)
|
|
|
|
usb_devices = new_devices
|
|
|
|
write_usb_devices_json()
|
|
|
|
|
|
|
|
def write_usb_devices_json():
|
|
|
|
global usb_devices
|
|
|
|
json_str = json.dumps([ob.to_dict() for ob in usb_devices])
|
|
|
|
with open('/tmp/usb_extras.json', 'w') as f:
|
|
|
|
f.write(json_str)
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
|
|
|
|
################################
|
|
|
|
## Utility Functions
|
|
|
|
################################
|
|
|
|
def set_usb_extras_state(state):
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("USB Extras State: {}".format(state))
|
2021-12-29 18:50:03 +00:00
|
|
|
try:
|
|
|
|
with open("/tmp/.usb_extras_state", "w") as f:
|
|
|
|
f.write(state)
|
|
|
|
os.system("sync")
|
|
|
|
return True
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
################################
|
|
|
|
## HTTP Server Functions
|
|
|
|
################################
|
|
|
|
class NoCacheHTTPRequestHandler(
|
|
|
|
SimpleHTTPRequestHandler
|
|
|
|
):
|
|
|
|
def send_response_only(self, code, message=None):
|
|
|
|
super().send_response_only(code, message)
|
|
|
|
self.send_header('Cache-Control', 'no-store, must-revalidate')
|
|
|
|
self.send_header('Expires', '0')
|
|
|
|
|
|
|
|
def web_handler_from(directory):
|
|
|
|
def _init(self, *args, **kwargs):
|
|
|
|
return NoCacheHTTPRequestHandler.__init__(self, *args, directory=self.directory, **kwargs)
|
|
|
|
return type(f'HandlerFrom<{directory}>',
|
|
|
|
(NoCacheHTTPRequestHandler,),
|
|
|
|
{'__init__': _init, 'directory': directory})
|
|
|
|
|
|
|
|
################################
|
|
|
|
## Detection Functions
|
|
|
|
################################
|
|
|
|
def check_partition_for_opendime(partition):
|
|
|
|
is_opendime = False
|
|
|
|
if mount_partition(partition, "temp_check"):
|
|
|
|
if os.path.isfile("/mnt/usb_extras/temp_check/support/opendime.png"):
|
|
|
|
is_opendime = True
|
|
|
|
unmount_partition("temp_check")
|
|
|
|
return is_opendime
|
|
|
|
|
|
|
|
################################
|
|
|
|
## Device Handlers
|
|
|
|
################################
|
|
|
|
usb_device_id = 0
|
|
|
|
|
|
|
|
class UsbDeviceHandler:
|
|
|
|
def __init__(self):
|
|
|
|
global usb_device_id
|
|
|
|
self.id = usb_device_id
|
|
|
|
usb_device_id = usb_device_id + 1
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
class OpendimeHandler(UsbDeviceHandler):
|
|
|
|
|
|
|
|
def __init__(self, block_device, partition):
|
|
|
|
super().__init__()
|
|
|
|
self.device = block_device
|
|
|
|
self.device_type = "opendime"
|
|
|
|
self.partition = partition
|
|
|
|
self.folder_name = f"opendime_{self.id}"
|
|
|
|
self.state = "loading_1"
|
|
|
|
self.http_server = None
|
|
|
|
self.http_server_thread = None
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
dict = {}
|
|
|
|
dict["id"] = self.id
|
|
|
|
dict["device_type"] = self.device_type
|
|
|
|
dict["device"] = self.device
|
|
|
|
dict["partition"] = self.partition
|
|
|
|
dict["folder_name"] = self.folder_name
|
|
|
|
dict["port"] = self.port
|
|
|
|
dict["state"] = self.state
|
|
|
|
return dict
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
try:
|
|
|
|
if mount_partition(self.partition, self.folder_name, "rw"):
|
|
|
|
# Check device state
|
|
|
|
self.state = "loading_2"
|
|
|
|
try:
|
|
|
|
readme_file = f"/mnt/usb_extras/{self.folder_name}/README.txt"
|
|
|
|
private_key_file = f"/mnt/usb_extras/{self.folder_name}/private-key.txt"
|
|
|
|
if os.path.isfile(readme_file):
|
|
|
|
with open(readme_file) as f:
|
|
|
|
content = f.read()
|
|
|
|
if "This Opendime is fresh and unused. It hasn't picked a private key yet." in content:
|
|
|
|
self.state = "new"
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message(" Opendime in state 'new'")
|
2021-12-29 18:50:03 +00:00
|
|
|
|
|
|
|
if os.path.isfile(private_key_file):
|
|
|
|
with open(private_key_file) as f:
|
|
|
|
content = f.read()
|
|
|
|
if "SEALED" in content:
|
|
|
|
self.state = "sealed"
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message(" Opendime in state 'sealed'")
|
2021-12-29 18:50:03 +00:00
|
|
|
else:
|
|
|
|
self.state = "unsealed"
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message(" Opendime in state 'unsealed'")
|
2021-12-29 18:50:03 +00:00
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
self.state = "error_reading_opendime"
|
|
|
|
|
|
|
|
self.port = 8010 + (self.id % 10)
|
|
|
|
self.http_server = HTTPServer(('', self.port), web_handler_from(f"/mnt/usb_extras/{self.folder_name}"))
|
|
|
|
self.http_server_thread = Thread(target = self.http_server.serve_forever)
|
|
|
|
self.http_server_thread.setDaemon(True)
|
|
|
|
self.http_server_thread.start()
|
|
|
|
return True
|
|
|
|
else:
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("Error mounting partition for opendime")
|
2021-12-29 18:50:03 +00:00
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
|
|
unmount_partition(self.folder_name)
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("Opendime Start Exception: {}".format(str(e)))
|
2021-12-29 18:50:03 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
try:
|
|
|
|
if self.http_server:
|
|
|
|
self.http_server.shutdown()
|
|
|
|
unmount_partition(self.folder_name)
|
|
|
|
except Exception as e:
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("Opendime Stop Exception: {}".format(str(e)))
|
2021-12-29 18:50:03 +00:00
|
|
|
|
|
|
|
################################
|
|
|
|
## check_usb_devices()
|
|
|
|
################################
|
|
|
|
def check_usb_devices():
|
|
|
|
try:
|
|
|
|
# if new event, reset state
|
|
|
|
# if no new event and in state (mounted), jump to state machine
|
|
|
|
|
|
|
|
# Set initial state
|
|
|
|
set_usb_extras_state("detecting")
|
|
|
|
os.system("umount /mnt/usb_extras")
|
|
|
|
|
|
|
|
# Detect drives
|
|
|
|
drives = find_unmounted_drives()
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message(f"Drives: {drives}")
|
2021-12-29 18:50:03 +00:00
|
|
|
|
|
|
|
# Check exactly one extra drive found
|
|
|
|
drive_count = len(drives)
|
|
|
|
if drive_count == 0:
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("No USB extras found.")
|
2021-12-29 18:50:03 +00:00
|
|
|
else:
|
|
|
|
set_usb_extras_state("processing")
|
|
|
|
for drive in drives:
|
|
|
|
# Check drive for partitions
|
|
|
|
drive = drives[0]
|
|
|
|
partitions = find_partitions_for_drive(drive)
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message(f"Drive {drive} paritions: {partitions}")
|
2021-12-29 18:50:03 +00:00
|
|
|
|
|
|
|
num_partitions = len(partitions)
|
|
|
|
if num_partitions == 0:
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("No partitions found. Nothing to do.")
|
2021-12-29 18:50:03 +00:00
|
|
|
elif num_partitions == 1:
|
|
|
|
# Process partition
|
|
|
|
partition = partitions[0]
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("One partition found! Scanning...")
|
2021-12-29 18:50:03 +00:00
|
|
|
if check_partition_for_opendime(partition):
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("Found Opendime!")
|
2021-12-29 18:50:03 +00:00
|
|
|
opendime = OpendimeHandler(drive, partition)
|
|
|
|
if opendime.start():
|
|
|
|
add_usb_device(opendime)
|
|
|
|
else:
|
|
|
|
opendime.stop()
|
|
|
|
else:
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message(f"Drive {drive} could not be detected.")
|
2021-12-29 18:50:03 +00:00
|
|
|
else:
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message(f"{num_partitions} partitions found. Not sure what to do.")
|
2021-12-29 18:50:03 +00:00
|
|
|
|
|
|
|
# Successful scan post init or usb action detected, mark homepage refresh
|
|
|
|
os.system("touch /tmp/homepage_needs_refresh")
|
|
|
|
|
|
|
|
except Exception as e:
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("Exception: {}".format(str(e)))
|
2021-12-29 18:50:03 +00:00
|
|
|
set_usb_extras_state("error")
|
|
|
|
reset_usb_devices()
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("Caught exception. Delaying 30s.")
|
2021-12-29 18:50:03 +00:00
|
|
|
time.sleep(30)
|
|
|
|
|
|
|
|
|
|
|
|
################################
|
|
|
|
## Main
|
|
|
|
################################
|
|
|
|
def main():
|
|
|
|
# Setup
|
|
|
|
os.system("mkdir -p /mnt/usb_extras")
|
|
|
|
|
|
|
|
# Start fresh and check USB devices once on startup
|
|
|
|
unmount_partition("*")
|
|
|
|
reset_usb_devices()
|
|
|
|
check_usb_devices()
|
|
|
|
|
|
|
|
# Monitor USB and re-check on add/remove
|
|
|
|
context = pyudev.Context()
|
|
|
|
monitor = pyudev.Monitor.from_netlink(context)
|
|
|
|
monitor.filter_by(subsystem='usb')
|
|
|
|
# this is module level logger, can be ignored
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("Starting to monitor for usb")
|
2021-12-29 18:50:03 +00:00
|
|
|
monitor.start()
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("Waiting on USB Event...")
|
2021-12-29 18:50:03 +00:00
|
|
|
set_usb_extras_state("waiting")
|
|
|
|
for device in iter(monitor.poll, None):
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("")
|
|
|
|
log_message("Got USB event: %s", device.action)
|
2021-12-29 18:50:03 +00:00
|
|
|
if device.action == 'add':
|
|
|
|
check_usb_devices()
|
|
|
|
else:
|
|
|
|
# HANDLE DEVICE REMOVAL BETTER? This resets all and re-scans
|
|
|
|
reset_usb_devices()
|
|
|
|
check_usb_devices()
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("Waiting on USB Event...")
|
2021-12-29 18:50:03 +00:00
|
|
|
set_usb_extras_state("waiting")
|
|
|
|
|
|
|
|
|
|
|
|
@atexit.register
|
|
|
|
def goodbye():
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("ATEXIT: Resetting devices")
|
2021-12-29 18:50:03 +00:00
|
|
|
unmount_partition("*")
|
|
|
|
reset_usb_devices()
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("ATEXIT: Done")
|
2021-12-29 18:50:03 +00:00
|
|
|
|
|
|
|
# This is the main entry point for the program
|
|
|
|
if __name__ == "__main__":
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
main()
|
|
|
|
except Exception as e:
|
|
|
|
set_usb_extras_state("error")
|
2022-02-17 03:47:46 +00:00
|
|
|
log_message("Main Exception: {}".format(str(e)))
|
|
|
|
log_message("Caught exception. Delaying 30s.")
|
2021-12-29 18:50:03 +00:00
|
|
|
unmount_partition("*")
|
|
|
|
reset_usb_devices()
|
|
|
|
time.sleep(30)
|