Skip to content

Commit

Permalink
New simple package structure: The __main__.py is the entry point to p…
Browse files Browse the repository at this point in the history
…ackage with optional arguments (python3 -m swiftguard [--gui/--cli]). It starts app.py or cli.py, which in turn initiate the whole app or just the worker in cli mode. Several improvements and bugfixes
  • Loading branch information
Lennolium committed Oct 1, 2023
1 parent 3b349f7 commit c43386a
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 118 deletions.
69 changes: 69 additions & 0 deletions src/swiftguard/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python3

"""
__main__.py: TODO: Headline...
start in cli: python3 -m swiftguard or python3 -m swiftguard --cli
or in gui: python3 -m swiftguard --gui.
TODO: Description...
"""

# Header.
__author__ = "Lennart Haack"
__email__ = "[email protected]"
__license__ = "GNU GPLv3"
__version__ = "0.0.2"
__build__ = "2023.2"
__date__ = "2023-09-28"
__status__ = "Prototype"

# Imports.
import argparse

if __name__ == "__main__":
# Create argument parser to let the user choose between CLI and GUI.
parser = argparse.ArgumentParser(
prog="swiftGuard",
description=f"swiftGuard v{__version__} ({__build__})\n\n"
"Anti-forensic macOS tray application designed to "
"safeguard your system by monitoring USB ports. It ensures your "
"device's security by automatically initiating either a system "
"shutdown or hibernation if an unauthorized device connects or a "
"connected device is unplugged. It offers the flexibility to whitelist"
" designated devices, to select an action to be executed and to set a "
"countdown timer, allowing to disarm the shutdown process."
"It can be run as a CLI or as a GUI, see following options.",
epilog="For more information: "
"https://github.com/Lennolium/swiftGuard",
formatter_class=argparse.RawDescriptionHelpFormatter,
)

# CLI argument.
parser.add_argument(
"-c", "--cli", action="store_true", help="starts in CLI mode (default)"
)

# GUI argument.
parser.add_argument(
"-g",
"--gui",
action="store_true",
help="starts in GUI mode (recommended for desktop)",
)

# Parse arguments.
args = parser.parse_args()

# Start GUI: We just import the GUI here, because it adds a lot of
# dependencies, and the CLI should be as lightweight as possible.
if args.gui:
from swiftguard import app

app.main()

# Start CLI (default).
else:
from swiftguard import cli

cli.main()
15 changes: 10 additions & 5 deletions src/swiftguard/swiftguard.py → src/swiftguard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,17 @@
from PySide6.QtGui import QAction, QIcon, QKeySequence, QPixmap
from PySide6.QtWidgets import QApplication, QMenu, QMessageBox, QSystemTrayIcon

from helpers import config_load, config_write, startup, usb_devices
# pylint: disable=unused-import
# noinspection PyUnresolvedReferences
from resources import resources_rc # noqa: F401
from utils import LogCount, add_handler, create_logger
from worker import Worker
from swiftguard.resources import resources_rc # noqa: F401
from swiftguard.utils.helpers import (
config_load,
config_write,
startup,
usb_devices,
)
from swiftguard.utils.log import LogCount, add_handler, create_logger
from swiftguard.utils.workers import WorkerUsb

# Constants.
CURRENT_PLATFORM = platform.uname()[0].upper()
Expand Down Expand Up @@ -664,7 +669,7 @@ def worker_handle(self, state):
if state == "Guarding":
# Start the worker thread.
self.worker_thread = QThread()
self.worker = Worker(self.config)
self.worker = WorkerUsb(self.config)

self.worker.tampered.connect(self.manipulation)
# self.worker.executed.connect(self.action_executed)
Expand Down
110 changes: 110 additions & 0 deletions src/swiftguard/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python3

"""
cli.py: TODO: Headline...
TODO: Description...
"""

# Header.
__author__ = "Lennart Haack"
__email__ = "[email protected]"
__license__ = "GNU GPLv3"
__version__ = "0.0.1"
__date__ = "2023-10-01"
__status__ = "Prototype/Development/Production"

# Imports.
import signal
import sys

from swiftguard.utils.helpers import startup
from swiftguard.utils.log import LogCount, add_handler, create_logger
from swiftguard.utils.workers import WorkerUsb

# Root logger and log counter.
LOG_COUNT = LogCount()
LOGGER = create_logger(LOG_COUNT)


# Handle uncaught exceptions and log them to CRITICAL.
def handle_exception(exc_type, exc_value, exc_traceback):
# Do not log KeyboardInterrupt (Ctrl+C).
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return

LOGGER.critical(
"Uncaught Exception:",
exc_info=(exc_type, exc_value, exc_traceback),
)


def exit_handler(signum, frame):
"""
The exit_handler function is a signal handler that catches the
SIGINT and SIGTERM signals. It then prints out a message to the
log file, and exits with status 0.
:param signum: Identify the signal that caused the exit_handler
to be called
:param frame: Reference the frame object that called function
:return: The exit_handler function
"""

LOGGER.info("Exiting the application properly ...")
sys.exit(0)


def main():
"""
The main function is the entry point of the standalone script.
It initializes and starts the main worker loop. It is not possible
to defuse the countdown if a delay is set and the script is running
standalone.
NOTE: For further instructions for using this script standalone,
please refer to the header of this file.
:return: None
"""

# Register handlers for clean exit of program.
for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGQUIT]:
signal.signal(sig, exit_handler)

# Set the exception hook.
sys.excepthook = handle_exception

# Startup.
config = startup()

log_dest = config["Application"]["log"].split(", ")
for dest in log_dest:
# Logging to file is default (can not be disabled).
if dest == "file":
continue
elif dest == "syslog":
add_handler(LOGGER, "syslog")

elif dest == "stdout":
add_handler(LOGGER, "stdout")

# Get log level from config file and apply it to the root logger.
# 1 = DEBUG, 2 = INFO, 3 = WARNING, 4 = ERROR, 5 = CRITICAL.
log_level = int(config["Application"]["log_level"]) * 10
LOGGER.setLevel(log_level)

# Create worker and start main worker loop.
print("Start guarding the USB ports ...", file=sys.stdout)
worker = WorkerUsb(config)
worker.loop()

# Exit program.
sys.exit(0)


# You can also just start the script with python3 -m swiftguard.cli
# instead of python3 -m swiftguard.
if __name__ == "__main__":
main()
20 changes: 20 additions & 0 deletions src/swiftguard/install/dev.lennolium.swiftguard.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>Label</key>
<string>dev.lennolium.swiftguard</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/open</string>
<string>/Applications/swiftGuard.app</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
16 changes: 16 additions & 0 deletions src/swiftguard/install/swiftguard.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[Unit]
Description=swiftGuard
After=multi-user.target
StartLimitBurst=5
StartLimitIntervalSec=10

[Service]
Type=simple
Restart=on-failure
RestartSec=1
User= ... TODO
ExecStart=/usr/bin/python3 /path/script.py ... TODO
Type=simple

[Install]
WantedBy=multi-user.target
10 changes: 6 additions & 4 deletions src/swiftguard/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
__author__ = "Lennart Haack"
__email__ = "[email protected]"
__license__ = "GNU GPLv3"
__version__ = "0.0.1"
__date__ = "2023-09-30"
__status__ = "Prototype/Development/Production"
__version__ = "0.0.2"
__build__ = "2023.2"
__date__ = "2023-09-28"
__status__ = "Prototype"

# Imports.
from .log import LogCount, add_handler, create_logger
from .autostart import add_autostart # noqa: F401
from .log import LogCount, add_handler, create_logger # noqa: F401
98 changes: 98 additions & 0 deletions src/swiftguard/utils/autostart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python3

"""
autostart.py: TODO: Headline...
TODO: Description...
"""

# Header.
__author__ = "Lennart Haack"
__email__ = "[email protected]"
__license__ = "GNU GPLv3"
__version__ = "0.0.2"
__build__ = "2023.2"
__date__ = "2023-09-28"
__status__ = "Prototype"

# Imports.
import logging
import os
import platform
import shutil

# Constants.
CURRENT_PLATFORM = platform.uname()[0].upper()
USER_HOME = os.path.expanduser("~")
APP_PATH = os.path.dirname(os.path.realpath(__file__))[:-6]
CONFIG_FILE = f"{USER_HOME}/Library/Preferences/swiftguard/swiftguard.ini"
LOG_FILE = f"{USER_HOME}/Library/Logs/swiftguard/swiftguard.log"

# Child logger.
LOGGER = logging.getLogger(__name__)


def add_autostart():
# TODO: docstring.
# macOS: Create launch agent.
if CURRENT_PLATFORM.startswith("DARWIN"):
launch_agent_dest = (
f"{USER_HOME}/Library/LaunchAgents/dev.lennolium.swiftguard.plist"
)
try:
# Create LaunchAgents directory if it does not exist.
if not os.path.isdir(os.path.dirname(launch_agent_dest)):
os.mkdir(os.path.dirname(launch_agent_dest))

LOGGER.info(
f"Created directory "
f"{os.path.dirname(launch_agent_dest)}."
)

# Copy the plist to the LaunchAgents directory.
shutil.copy(
os.path.join(
APP_PATH, "install", "dev.lennolium.swiftguard.plist"
),
launch_agent_dest,
)

except Exception as e:
LOGGER.error(
f"Autostart could not be configured. Could not copy "
f"launch agent plist to {launch_agent_dest}. \n"
f"Error: {e}"
)

# Linux: Create systemd service (WiP).
else:
raise NotImplementedError("Linux is not supported yet.")
# # Debian based, e.g. Ubuntu: Create systemd service.
# # See https://linuxhandbook.com/create-systemd-services/
# user_systemd_dest =
# f"{USER_HOME}/.config/systemd/user/"
# systemd_service_dest =
# "/etc/systemd/system/swiftguard.service"
# systemd_service_dest_alt = "
# /usr/systemd/system/swiftguard.service"
#
# # Non-Debian based, e.g. Arch Linux: Create systemd service.
# systemd_service_dest_alt2 =
# "/usr/lib/systemd/system/swiftguard.service"
#
# # Copy the service to the systemd directory.
# shutil.copy(
# os.path.join(APP_PATH, "install", "swiftguard.service"),
# systemd_service_dest,
# )
#
# # User: Reload the systemd daemon, enable and start the
# # service.
# os.system("systemctl --user daemon-reload")
# os.system("systemctl --user enable swiftguard.service")
# os.system("systemctl --user start swiftguard.service")
#
# # System.
# os.system("systemctl daemon-reload")
# os.system("systemctl enable swiftguard.service")
# os.system("systemctl start swiftguard.service")
20 changes: 20 additions & 0 deletions src/swiftguard/utils/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env python3

"""
conf.py: TODO: Headline...
TODO: Description...
"""

# Header.
__author__ = "Lennart Haack"
__email__ = "[email protected]"
__license__ = "GNU GPLv3"
__version__ = "0.0.2"
__build__ = "2023.2"
__date__ = "2023-09-28"
__status__ = "Prototype"

# Imports.

# TODO: hier die ganzen config funktionen einfügen...
Loading

0 comments on commit c43386a

Please sign in to comment.