diff --git a/ui/bin/opensnitch-ui b/ui/bin/opensnitch-ui index 8abbfa11a0..39982b84f0 100755 --- a/ui/bin/opensnitch-ui +++ b/ui/bin/opensnitch-ui @@ -40,11 +40,13 @@ if dist_path not in sys.path: from opensnitch.service import UIService from opensnitch.config import Config from opensnitch.utils import Themes, Utils, Versions +from opensnitch.utils.xdg import xdg_runtime_dir import opensnitch.ui_pb2 from opensnitch.ui_pb2_grpc import add_UIServicer_to_server def on_exit(): server.stop(0) + lockfile.unlock() app.quit() sys.exit(0) @@ -92,75 +94,91 @@ Examples: except Exception: pass - app = QtWidgets.QApplication(sys.argv) - if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'): - app.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) - thm = Themes.instance() - thm.load_theme(app) - - Utils.create_socket_dirs() - cfg = Config.get() - if args.socket == None: - # default - args.socket = "unix:///tmp/osui.sock" - - addr = cfg.getSettings(Config.DEFAULT_SERVER_ADDR) - if addr != None and addr != "": - if addr.startswith("unix://"): - if not os.path.exists(os.path.dirname(addr[7:])): - print("WARNING: unix socket path does not exist, using unix:///tmp/osui.sock, ", addr) - else: - args.socket = addr + try: + app = QtWidgets.QApplication(sys.argv) + lockfile = QtCore.QLockFile(os.path.join(xdg_runtime_dir, 'osui.lock')) + + if lockfile.tryLock(100): + if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'): + app.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) + thm = Themes.instance() + thm.load_theme(app) + + Utils.create_socket_dirs() + cfg = Config.get() + if args.socket == None: + # default + args.socket = "unix:///tmp/osui.sock" + + addr = cfg.getSettings(Config.DEFAULT_SERVER_ADDR) + if addr != None and addr != "": + if addr.startswith("unix://"): + if not os.path.exists(os.path.dirname(addr[7:])): + print("WARNING: unix socket path does not exist, using unix:///tmp/osui.sock, ", addr) + else: + args.socket = addr + else: + args.socket = addr + + print("Using server address:", args.socket) + + maxmsglencfg = cfg.getSettings(Config.DEFAULT_SERVER_MAX_MESSAGE_LENGTH) + if maxmsglencfg == '4MiB': + maxmsglen = 4194304 + elif maxmsglencfg == '8MiB': + maxmsglen = 8388608 + elif maxmsglencfg == '16MiB': + maxmsglen = 16777216 + else: + maxmsglen = 4194304 + + print("gRPC Max Message Length:", maxmsglencfg) + print(" Bytes:", maxmsglen) + + service = UIService(app, on_exit) + # @doc: https://grpc.github.io/grpc/python/grpc.html#server-object + server = grpc.server(futures.ThreadPoolExecutor(), + options=( + # https://github.com/grpc/grpc/blob/master/doc/keepalive.md + # https://grpc.github.io/grpc/core/group__grpc__arg__keys.html + # send keepalive ping every 5 second, default is 2 hours) + ('grpc.keepalive_time_ms', 5000), + # after 5s of inactivity, wait 20s and close the connection if + # there's no response. + ('grpc.keepalive_timeout_ms', 20000), + ('grpc.keepalive_permit_without_calls', True), + ('grpc.max_send_message_length', maxmsglen), + ('grpc.max_receive_message_length', maxmsglen), + )) + + add_UIServicer_to_server(service, server) + + if args.socket.startswith("unix://"): + socket = args.socket[7:] + socket = os.path.abspath(socket) + server.add_insecure_port("unix:%s" % socket) else: - args.socket = addr - - print("Using server address:", args.socket) - - maxmsglencfg = cfg.getSettings(Config.DEFAULT_SERVER_MAX_MESSAGE_LENGTH) - if maxmsglencfg == '4MiB': - maxmsglen = 4194304 - elif maxmsglencfg == '8MiB': - maxmsglen = 8388608 - elif maxmsglencfg == '16MiB': - maxmsglen = 16777216 - else: - maxmsglen = 4194304 - - print("gRPC Max Message Length:", maxmsglencfg) - print(" Bytes:", maxmsglen) - - service = UIService(app, on_exit) - # @doc: https://grpc.github.io/grpc/python/grpc.html#server-object - server = grpc.server(futures.ThreadPoolExecutor(), - options=( - # https://github.com/grpc/grpc/blob/master/doc/keepalive.md - # https://grpc.github.io/grpc/core/group__grpc__arg__keys.html - # send keepalive ping every 5 second, default is 2 hours) - ('grpc.keepalive_time_ms', 5000), - # after 5s of inactivity, wait 20s and close the connection if - # there's no response. - ('grpc.keepalive_timeout_ms', 20000), - ('grpc.keepalive_permit_without_calls', True), - ('grpc.max_send_message_length', maxmsglen), - ('grpc.max_receive_message_length', maxmsglen), - )) - - add_UIServicer_to_server(service, server) - - if args.socket.startswith("unix://"): - socket = args.socket[7:] - socket = os.path.abspath(socket) - server.add_insecure_port("unix:%s" % socket) - else: - server.add_insecure_port(args.socket) - - # https://stackoverflow.com/questions/5160577/ctrl-c-doesnt-work-with-pyqt - signal.signal(signal.SIGINT, signal.SIG_DFL) + server.add_insecure_port(args.socket) + + # https://stackoverflow.com/questions/5160577/ctrl-c-doesnt-work-with-pyqt + signal.signal(signal.SIGINT, signal.SIG_DFL) + + # print "OpenSnitch UI service running on %s ..." % socket + server.start() + app.exec_() + + else: + errortxt = "OpenSnitch UI is already running!" + print(errortxt, "\n") + errormsg = QtWidgets.QMessageBox() + errormsg.setIcon(QtWidgets.QMessageBox.Warning) + errormsg.setWindowTitle("Error") + errormsg.setText(errortxt) + errormsg.setStandardButtons(QtWidgets.QMessageBox.Ok) + errormsg.exec() - try: - # print "OpenSnitch UI service running on %s ..." % socket - server.start() - app.exec_() except KeyboardInterrupt: on_exit() + finally: + lockfile.unlock() diff --git a/ui/opensnitch/dialogs/stats.py b/ui/opensnitch/dialogs/stats.py index 68ed3cfa6d..0a077f7898 100644 --- a/ui/opensnitch/dialogs/stats.py +++ b/ui/opensnitch/dialogs/stats.py @@ -23,6 +23,7 @@ from opensnitch.customwidgets.generictableview import GenericTableModel from opensnitch.customwidgets.addresstablemodel import AddressTableModel from opensnitch.utils import Message, QuickHelp, AsnDB, Icons +from opensnitch.utils.xdg import xdg_current_desktop from opensnitch.actions import Actions from opensnitch.rules import Rule, Rules @@ -277,8 +278,6 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): def __init__(self, parent=None, address=None, db=None, dbname="db", appicon=None): super(StatsDialog, self).__init__(parent) - self._current_desktop = os.environ['XDG_CURRENT_DESKTOP'] if os.environ.get("XDG_CURRENT_DESKTOP") != None else None - self.setWindowFlags(QtCore.Qt.Window) self.setupUi(self) self.setWindowIcon(appicon) @@ -625,7 +624,7 @@ def changeEvent(self, event): if event.type() == QtCore.QEvent.WindowStateChange: if event.oldState() & QtCore.Qt.WindowMinimized and event.oldState() & QtCore.Qt.WindowMaximized: #a previously minimized maximized window ... - if self.windowState() ^ QtCore.Qt.WindowMinimized and self._current_desktop == "KDE": + if self.windowState() ^ QtCore.Qt.WindowMinimized and xdg_current_desktop == "KDE": # is not minimized anymore, i.e. it was unminimized # docs: https://doc.qt.io/qt-5/qwidget.html#setWindowState self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) diff --git a/ui/opensnitch/service.py b/ui/opensnitch/service.py index db3dbab678..5ba5979ced 100644 --- a/ui/opensnitch/service.py +++ b/ui/opensnitch/service.py @@ -26,6 +26,7 @@ from opensnitch.database import Database from opensnitch.utils import Utils, CleanerTask, Themes from opensnitch.utils import Message, languages +from opensnitch.utils.xdg import Autostart class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject): _new_remote_trigger = QtCore.pyqtSignal(str, ui_pb2.PingRequest) @@ -87,6 +88,7 @@ def __init__(self, app, on_exit): self._msg = QtWidgets.QMessageBox() self._remote_lock = Lock() self._remote_stats = {} + self._autostart = Autostart() self.translator = None self._init_translation() @@ -181,11 +183,27 @@ def _setup_tray(self): self._menu_enable_fw = self._menu.addAction(self.MENU_ENTRY_FW_DISABLE) self._menu_enable_fw.setEnabled(False) self._menu_enable_fw.triggered.connect(self._on_enable_interception_clicked) + + self._menu.addSeparator() + self._menu_autostart = self._menu.addAction("Autostart") + self._menu_autostart.setCheckable(True) + self._menu_autostart.setChecked(self._autostart.isEnabled()) + self._menu_autostart.triggered.connect(self._on_switch_autostart) + self._menu.addSeparator() + self._menu.addAction(self.MENU_ENTRY_HELP).triggered.connect( lambda: QtGui.QDesktopServices.openUrl(QtCore.QUrl(Config.HELP_CONFIG_URL)) ) self._menu.addAction(self.MENU_ENTRY_CLOSE).triggered.connect(self._on_close) + self._menu.aboutToShow.connect(self._on_show_menu) + + def _on_switch_autostart(self): + self._autostart.enable(self._menu_autostart.isChecked()) + + def _on_show_menu(self): + self._menu_autostart.setChecked(self._autostart.isEnabled()) + def _show_gui_if_tray_not_available(self): """If the system tray is not available or ready, show the GUI after 10s. This delay helps to skip showing up the GUI when DEs' autologin is on. diff --git a/ui/opensnitch/utils/xdg.py b/ui/opensnitch/utils/xdg.py index 9fed4ade7c..5209167134 100644 --- a/ui/opensnitch/utils/xdg.py +++ b/ui/opensnitch/utils/xdg.py @@ -1,5 +1,101 @@ import os +import re +import shutil -_user_home = os.path.expanduser('~') -xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or os.path.join(_user_home, '.config') +# https://github.com/takluyver/pyxdg/blob/1d23e483ae869ee9532aca43b133cc43f63626a3/xdg/BaseDirectory.py +def get_runtime_dir(strict=True): + try: + return os.environ['XDG_RUNTIME_DIR'] + except KeyError: + if strict: + raise + + import getpass + fallback = '/tmp/opensnitch-' + getpass.getuser() + create = False + + try: + # This must be a real directory, not a symlink, so attackers can't + # point it elsewhere. So we use lstat to check it. + st = os.lstat(fallback) + except OSError as e: + import errno + if e.errno == errno.ENOENT: + create = True + else: + raise + else: + # The fallback must be a directory + if not stat.S_ISDIR(st.st_mode): + os.unlink(fallback) + create = True + # Must be owned by the user and not accessible by anyone else + elif (st.st_uid != os.getuid()) \ + or (st.st_mode & (stat.S_IRWXG | stat.S_IRWXO)): + os.rmdir(fallback) + create = True + + if create: + os.mkdir(fallback, 0o700) + + return fallback + + +class Autostart(): + def __init__(self): + desktopFile = 'opensnitch_ui.desktop' + self.systemDesktop = os.path.join('/usr/share/applications', desktopFile) + self.systemAutostart = os.path.join('/etc/xdg/autostart', desktopFile) + if not os.path.isfile(self.systemAutostart) and os.path.isfile('/usr' + self.systemAutostart): + self.systemAutostart = '/usr' + self.systemAutostart + self.userAutostart = os.path.join(xdg_config_home, 'autostart', desktopFile) + + def createUserDir(self): + if not os.path.isdir(xdg_config_home): + os.makedirs(xdg_config_home, 0o700) + if not os.path.isdir(os.path.dirname(self.userAutostart)): + os.makedirs(os.path.dirname(self.userAutostart), 0o755) + + def isEnabled(self): + ret = False + if os.path.isfile(self.userAutostart): + ret = True + lines = open(self.userAutostart, 'r').readlines() + for line in lines: + if re.search("^Hidden=true", line, re.IGNORECASE): + ret = False + break + elif os.path.isfile(self.systemAutostart): + ret = True + return ret + + def enable(self, mode=True): + self.createUserDir() + if mode == True: + if os.path.isfile(self.systemAutostart) and os.path.isfile(self.userAutostart): + os.remove(self.userAutostart) + elif os.path.isfile(self.systemDesktop): + try: + shutil.copyfile(self.systemDesktop, self.userAutostart) + except shutil.SameFileError: + pass + else: + if os.path.isfile(self.systemAutostart): + try: + shutil.copyfile(self.systemAutostart, self.userAutostart) + except shutil.SameFileError: + pass + with open(self.userAutostart, 'a') as f: + f.write('Hidden=true\n') + elif os.path.isfile(self.userAutostart): + os.remove(self.userAutostart) + + def disable(self): + self.enable(False) + + +_home = os.path.expanduser('~') +xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or os.path.join(_home, '.config') +xdg_runtime_dir = get_runtime_dir(False) +xdg_current_desktop = os.environ.get('XDG_CURRENT_DESKTOP') diff --git a/ui/resources/opensnitch_ui.desktop b/ui/resources/opensnitch_ui.desktop index 410510333d..782309faa9 100644 --- a/ui/resources/opensnitch_ui.desktop +++ b/ui/resources/opensnitch_ui.desktop @@ -1,7 +1,7 @@ [Desktop Entry] Type=Application Name=OpenSnitch -Exec=/bin/sh -c "pkill -15 opensnitch-ui; opensnitch-ui" +Exec=opensnitch-ui Icon=opensnitch-ui GenericName=OpenSnitch Firewall GenericName[hu]=OpenSnitch-tűzfal