Files

130 lines
3.5 KiB
Python
Raw Permalink Normal View History

2023-05-17 17:21:35 -07:00
#!/usr/bin/python3
import shutil
import re
import threading
from subprocess import Popen, run, DEVNULL, PIPE
import sys
2023-05-17 17:21:35 -07:00
2025-05-05 09:12:53 +09:00
USBGUARD_EXEC_NAME = shutil.which("usbguard")
DUNSTIFY_EXEC_NAME = shutil.which("dunstify")
2023-05-17 17:21:35 -07:00
open_notifications = {}
2026-02-27 23:38:39 -08:00
def parse_event_type_and_id(line):
2025-05-05 09:12:53 +09:00
if not line.startswith("[device] "):
return None, False
2025-05-05 09:12:53 +09:00
event_type = re.findall("(?<=\\[device\\] )[a-zA-Z]+", line)
2023-05-17 17:21:35 -07:00
if len(event_type) == 0:
return None, False
2025-05-05 09:12:53 +09:00
event_id = re.findall("(?<=id=)[0-9]+", line)
2023-05-17 17:21:35 -07:00
if len(event_id) == 0:
return None, False
2023-05-17 17:21:35 -07:00
return event_type[0], int(event_id[0])
def parse_event_properties(stream, count):
props = {}
for _ in range(count):
line = stream.readline()
try:
2025-05-05 09:12:53 +09:00
sep_ind = line.index("=")
2023-05-17 17:21:35 -07:00
prop_name = line[1:sep_ind]
2025-05-05 09:12:53 +09:00
props[prop_name] = line[sep_ind + 1 : -1]
if prop_name == "device_rule":
2023-05-17 17:21:35 -07:00
break
except ValueError:
continue
return props
def get_name_and_id_from_rule(rule):
name = re.findall('(?<=name ")[^"]+(?=")', rule)
if len(name) == 0:
2025-05-05 09:12:53 +09:00
name = ""
2023-05-17 17:21:35 -07:00
else:
name = name[0]
2025-05-05 09:12:53 +09:00
id = re.findall("(?<=id )[a-z0-9]{4}:[a-z0-9]{4}", rule)
2023-05-17 17:21:35 -07:00
if len(id) == 0:
2025-05-05 09:12:53 +09:00
id = ""
2023-05-17 17:21:35 -07:00
else:
id = id[0]
return name, id
def prompt_device_action(dev_id, name, long_id):
2025-05-05 09:12:53 +09:00
proc = Popen(
[
DUNSTIFY_EXEC_NAME,
"-p",
"-A",
"block,Block",
"-A",
"allow,Allow",
"-A",
"reject,Reject",
f"{name} ({long_id})",
"New Device",
],
stdout=PIPE,
text=True,
bufsize=0,
)
2023-05-17 17:21:35 -07:00
open_notifications[dev_id] = int(proc.stdout.readline())
2023-05-17 21:57:17 -07:00
option = proc.communicate()[0][:-1]
2023-05-17 17:21:35 -07:00
try:
open_notifications.pop(dev_id)
except KeyError:
pass
match option:
2025-05-05 09:12:53 +09:00
case "reject":
run([USBGUARD_EXEC_NAME, "reject-device", long_id])
2023-05-17 17:21:35 -07:00
2025-05-05 09:12:53 +09:00
case "allow":
run([USBGUARD_EXEC_NAME, "allow-device", long_id])
2023-05-17 17:21:35 -07:00
2023-05-17 21:57:17 -07:00
case _:
2025-05-05 09:12:53 +09:00
run([USBGUARD_EXEC_NAME, "block-device", long_id])
2023-05-17 17:21:35 -07:00
def close_notification(dev_id):
if dev_id in open_notifications:
notif_id = open_notifications.pop(dev_id)
2025-05-05 09:12:53 +09:00
run([DUNSTIFY_EXEC_NAME, "-C", str(notif_id)])
2023-05-17 17:21:35 -07:00
2025-05-05 09:12:53 +09:00
with Popen(
[USBGUARD_EXEC_NAME, "watch"],
stdin=DEVNULL,
stdout=PIPE,
text=True,
bufsize=0,
) as usbguard_proc:
2023-05-17 17:21:35 -07:00
new_devices = set()
first_line = (
usbguard_proc.stdout.readline()
) # get rid of initial connection message
2026-02-27 23:38:39 -08:00
for line in usbguard_proc.stdout:
event_type_result = parse_event_type_and_id(line)
2023-05-17 17:21:35 -07:00
if event_type_result is None:
continue
event_type, dev_id = event_type_result
2025-05-05 09:12:53 +09:00
if event_type not in ["PresenceChanged", "PolicyApplied"]:
2023-05-17 17:21:35 -07:00
continue
props = parse_event_properties(usbguard_proc.stdout, 3)
2025-05-05 09:12:53 +09:00
name, long_id = get_name_and_id_from_rule(props["device_rule"])
2023-05-17 17:21:35 -07:00
match event_type:
2025-05-05 09:12:53 +09:00
case "PresenceChanged":
if props["event"] == "Insert":
2023-05-17 17:21:35 -07:00
new_devices.add(dev_id)
else:
close_notification(dev_id)
new_devices.discard(dev_id)
2025-05-05 09:12:53 +09:00
case "PolicyApplied":
if props["target_new"] == "block":
threading.Thread(
target=prompt_device_action,
args=(dev_id, name, long_id),
).start()
2023-05-17 17:21:35 -07:00
new_devices.discard(dev_id)