#!@PYTHON@ # -*- coding: utf-8 -*- # # Copyright (C) 2010-2015 Red Hat, Inc. # # Authors: # Thomas Woerner # # This program 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 2 of the License, or # (at your option) any later version. # # This program 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 this program. If not, see . # import sys from PyQt5 import QtGui, QtCore, QtWidgets import gi gi.require_version('Notify', '0.7') from gi.repository import Notify import os from dbus.mainloop.pyqt5 import DBusQtMainLoop import functools from firewall import config from firewall.core.fw_nm import nm_is_imported, nm_get_zone_of_connection, \ nm_set_zone_of_connection, \ nm_get_dbus_interface, \ nm_get_connections from firewall.core.watcher import Watcher from firewall.client import FirewallClient import slip.dbus import dbus import signal import gettext gettext.textdomain(config.DOMAIN) _ = gettext.gettext PATH = [ ] for p in os.getenv("PATH").split(":"): if p not in PATH: PATH.append(p) def search_app(app): for p in PATH: _app = "%s/%s" % (p, app) if os.path.exists(_app): return _app return None NM_CONNECTION_EDITOR = "" for binary in [ "/usr/bin/nm-connection-editor", "/bin/nm-connection-editor", "/usr/bin/kde5-nm-connection-editor", "/bin/kde5-nm-connection-editor", "/usr/bin/kde-nm-connection-editor", "/bin/kde-nm-connection-editor" ]: if os.path.exists(binary): NM_CONNECTION_EDITOR = binary break PY2 = sys.version < '3' def escape(text): text = text.replace('&', '&') text = text.replace('>', '>') text = text.replace('<', '<') return text def fromUTF8(text): if PY2 and QtCore.QT_VERSION < 0x050000: return QtCore.QString.fromUtf8(text) return text # ZoneInterfaceEditor ######################################################### class ZoneInterfaceEditor(QtWidgets.QDialog): def __init__(self, fw, interface, zone): self.fw = fw self.interface = interface self.zone = None self.title = _("Select zone for interface '%s'") % self.interface QtWidgets.QDialog.__init__(self) self.create_ui(zone) def create_ui(self, zone): self.setWindowTitle(fromUTF8(escape(self.title))) self.rejected.connect(self.hide) self.resize(100, 50) vbox = QtWidgets.QVBoxLayout() vbox.setSpacing(6) label = QtWidgets.QLabel(fromUTF8(escape(self.title))) vbox.addWidget(label) self.combo = QtWidgets.QComboBox() self.fill_zone_combo() vbox.addWidget(self.combo) buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) self.ok_button = buttonBox.button(QtWidgets.QDialogButtonBox.Ok) buttonBox.accepted.connect(self.ok) buttonBox.rejected.connect(self.hide) vbox.addWidget(buttonBox) self.ok_button.setDisabled(True) self.combo.activated.connect(self.combo_changed) self.setLayout(vbox) self.set_zone(zone) def combo_changed(self): self.ok_button.setDisabled(self.get_zone() == self.zone) def set_zone(self, zone): self.zone = zone if zone == "": self.combo.setCurrentIndex(self.combo.findText( escape(_("Default Zone")))) else: self.combo.setCurrentIndex(self.combo.findText(self.zone)) self.combo_changed() def get_zone(self): text = str(self.combo.currentText()) if text == escape(_("Default Zone")): text = "" return text def fill_zone_combo(self): self.combo.clear() self.combo.addItem(fromUTF8(escape(_("Default Zone")))) for z in self.fw.getZones(): self.combo.addItem(z) def zones_changed(self): zone = self.get_zone() self.fill_zone_combo() self.set_zone(zone) def ok(self): self.fw.changeZoneOfInterface(self.get_zone(), self.interface) self.hide() # ZoneConnectionEditor ######################################################## class ZoneConnectionEditor(ZoneInterfaceEditor): def __init__(self, fw, connection, connection_name, zone): self.fw = fw self.connection = connection self.connection_name = connection_name self.zone = None self.title = _("Select zone for connection '%s'") % self.connection_name QtWidgets.QDialog.__init__(self) self.create_ui(zone) def ok(self): # apply changes try: nm_set_zone_of_connection(self.get_zone(), self.connection) except Exception: text = _("Failed to set zone {zone} for connection {connection_name}") QtWidgets.QMessageBox.warning(None, fromUTF8(escape(self.title)), escape(text.format( zone=self.get_zone(), connection_name=self.connection_name))) self.hide() # ZoneSourceEditor ############################################################ class ZoneSourceEditor(ZoneInterfaceEditor): def __init__(self, fw, source, zone): self.fw = fw self.source = source self.zone = None self.title = _("Select zone for source '%s'") % self.source QtWidgets.QDialog.__init__(self) self.create_ui(zone) def ok(self): self.fw.changeZoneOfSource(self.get_zone(), self.source) self.hide() # ShieldsEditor ######################################################### class ShieldsEditor(QtWidgets.QDialog): def __init__(self, fw, settings, shields_up, shields_down): self.fw = fw self.settings = settings self.shields_up = shields_up self.shields_down = shields_down self.title = _("Configure Shields Up/Down Zones") QtWidgets.QDialog.__init__(self) self.create_ui() def create_ui(self): self.setWindowTitle(fromUTF8(escape(self.title))) self.rejected.connect(self.hide) vbox = QtWidgets.QVBoxLayout() vbox.setSpacing(6) label = QtWidgets.QLabel(fromUTF8(escape( _("Here you can select the zones used for Shields Up and " "Shields Down.")))) label.setWordWrap(True) vbox.addWidget(label) label = QtWidgets.QLabel(fromUTF8(escape( _("This feature is useful for people using the default zones " "mostly. For users, that are changing zones of connections, it " "might be of limited use.")))) label.setWordWrap(True) vbox.addWidget(label) grid = QtWidgets.QGridLayout() grid.setSpacing(6) label = QtWidgets.QLabel(fromUTF8(escape(_("Shields Up Zone:")))) label.setWordWrap(True) grid.addWidget(label, 0, 0, 1, 1) self.shields_up_combo = QtWidgets.QComboBox() #self.fill_combo(self.shields_up_combo) #self.set_shields_up(self.shields_up) grid.addWidget(self.shields_up_combo, 0, 1, 1, 1) button = QtWidgets.QPushButton(_("Reset To Default")) button.clicked.connect(self.reset_shields_up) grid.addWidget(button, 0, 2, 1, 1) label = QtWidgets.QLabel(fromUTF8(escape(_("Shields Down Zone:")))) label.setWordWrap(True) grid.addWidget(label, 1, 0, 1, 1) self.shields_down_combo = QtWidgets.QComboBox() #self.fill_combo(self.shields_down_combo) #self.set_shields_down(self.shields_down) grid.addWidget(self.shields_down_combo, 1, 1, 1, 1) button = QtWidgets.QPushButton(_("Reset To Default")) button.clicked.connect(self.reset_shields_down) grid.addWidget(button, 1, 2, 1, 1) vbox.addLayout(grid) buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) self.ok_button = buttonBox.button(QtWidgets.QDialogButtonBox.Ok) buttonBox.accepted.connect(self.ok) buttonBox.rejected.connect(self.hide) vbox.addWidget(buttonBox) self.ok_button.setDisabled(True) self.shields_up_combo.activated.connect(self.shields_combo_changed) self.shields_down_combo.activated.connect(self.shields_combo_changed) self.setLayout(vbox) def shields_combo_changed(self): self.ok_button.setDisabled( self.get_shields_up() == self.shields_up and \ self.get_shields_down() == self.shields_down) def set_shields_up(self, zone): self.shields_up = zone if self.shields_up_combo.count() > 0: self.shields_up_combo.setCurrentIndex( self.shields_up_combo.findText(self.shields_up)) self.shields_combo_changed() def set_shields_down(self, zone): self.shields_down = zone if self.shields_down_combo.count() > 0: self.shields_down_combo.setCurrentIndex( self.shields_down_combo.findText(self.shields_down)) self.shields_combo_changed() def reset_shields_up(self): self.set_shields_up(self.shields_up) # remove user key to get fallback again self.settings.remove("shields-up") def reset_shields_down(self): self.set_shields_down(self.shields_down) # remove user key to get fallback again self.settings.remove("shields-down") def get_shields_up(self): return str(self.shields_up_combo.currentText()) def get_shields_down(self): return str(self.shields_down_combo.currentText()) def zones_changed(self): up_zone = self.shields_up if self.get_shields_up(): up_zone = self.get_shields_up() down_zone = self.shields_down if self.get_shields_down(): down_zone = self.get_shields_down() for z in self.fw.getZones(): self.shields_up_combo.addItem(z) self.shields_down_combo.addItem(z) self.set_shields_up(up_zone) self.set_shields_down(down_zone) def ok(self): if self.shields_up != self.get_shields_up(): self.settings.setValue("shields-up", self.get_shields_up()) if self.shields_down != self.get_shields_down(): self.settings.setValue("shields-down", self.get_shields_down()) self.settings.sync() self.hide() # AboutDialog ################################################################# class AboutDialog(QtWidgets.QDialog): def __init__(self, name, icon, version, url, copyright, authors, license): QtWidgets.QDialog.__init__(self) self.setWindowIcon(icon) self.setWindowTitle(fromUTF8(escape(_("About %s" % name)))) self.resize(500, 250) vbox = QtWidgets.QVBoxLayout() vbox.setSpacing(6) hbox = QtWidgets.QHBoxLayout() hbox.setSpacing(24) label = QtWidgets.QLabel() label.setPixmap(icon.pixmap(96)) label.setMinimumSize(96, 96) label.setMaximumSize(96, 96) hbox.addWidget(label) vbox2 = QtWidgets.QVBoxLayout() vbox2.setSpacing(3) label = QtWidgets.QLabel(name) font = label.font() font.setPointSize(font.pointSize()*2) font.setBold(True) label.setFont(font) vbox2.addWidget(label) vbox2.addWidget(QtWidgets.QLabel(version)) label = QtWidgets.QLabel("%s" % (url, url)) label.setTextFormat(QtCore.Qt.RichText) label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) label.setOpenExternalLinks(True) vbox2.addWidget(label) vbox2.addWidget(QtWidgets.QLabel(copyright)) hbox.addLayout(vbox2) vbox.addLayout(hbox) tabs = QtWidgets.QTabWidget() tabs.setStyleSheet("QTabWidget::tab { padding: 1px 1px 1px 1px; }") tab = QtWidgets.QWidget() vbox3 = QtWidgets.QVBoxLayout() textedit = QtWidgets.QPlainTextEdit() #textedit.setStyleSheet("QPlainTextEdit { border: 0; padding: 0; }") textedit.setReadOnly(True) textedit.setPlainText(fromUTF8("\n".join(authors))) vbox3.addWidget(textedit) tab.setLayout(vbox3) tabs.addTab(tab, fromUTF8(escape(_("Authors")))) tab = QtWidgets.QWidget() vbox3 = QtWidgets.QVBoxLayout() textedit = QtWidgets.QPlainTextEdit() #textedit.setStyleSheet("QPlainTextEdit { border: 0; padding: 0; }") textedit.setReadOnly(True) textedit.setPlainText(license) vbox3.addWidget(textedit) tab.setLayout(vbox3) tabs.addTab(tab, fromUTF8(escape(_("License")))) vbox.addWidget(tabs) buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Close) buttonBox.rejected.connect(self.hide) vbox.addWidget(buttonBox) self.setLayout(vbox) # TrayApplet ################################################################## class TrayApplet(QtWidgets.QSystemTrayIcon): def __init__(self): super(TrayApplet, self).__init__() self.name = _("Firewall Applet") self.prog = "firewall-applet" self.icon_name = "firewall-applet" self.icons = { "normal": QtGui.QIcon.fromTheme(self.icon_name), "error": QtGui.QIcon.fromTheme(self.icon_name+"-error"), "panic": QtGui.QIcon.fromTheme(self.icon_name+"-panic"), "normal-shields_up": QtGui.QIcon.fromTheme(self.icon_name+"-shields_up"), "normal-shields_down": QtGui.QIcon.fromTheme(self.icon_name+"-shields_down"), } self.timer = None self.mode = None self.blink = False self.blink_count = 0 self._blink = False self._blink_count = 0 self.show_inactive = False self.tooltip_messages = [ ] self.active_zones = { } self.connections = { } self.connections_name = { } self.default_zone = None self.zone_connection_editors = { } self.zone_interface_editors = { } self.zone_source_editors = { } # settings self.settings = QtCore.QSettings("firewall", "applet") # file system watcher self.watcher = Watcher(self.load_settings, 2) self.watcher.add_watch_file("/etc/firewall/applet.conf") self.watcher.add_watch_file(str(self.settings.fileName())) # about dialog self.about_dialog = AboutDialog(self.name, self.icons["normal"], config.VERSION, config.WEBSITE, config.COPYRIGHT, config.AUTHORS, config.LICENSE) # urgencies self.urgencies = { "noicon": QtWidgets.QSystemTrayIcon.NoIcon, "information": QtWidgets.QSystemTrayIcon.Information, "warning": QtWidgets.QSystemTrayIcon.Warning, "critical": QtWidgets.QSystemTrayIcon.Critical } # actions self.shieldsupAction = QtWidgets.QAction(fromUTF8(escape(_("Shields Up"))), self) self.shieldsupAction.setCheckable(True) self.shieldsupAction.setChecked(False) self.shieldsupAction.triggered.connect(self.shieldsup_changed_cb) self.notificationsAction = QtWidgets.QAction( fromUTF8(escape(_("Enable Notifications"))), self) self.notificationsAction.setCheckable(True) self.notificationsAction.setChecked(False) self.notificationsAction.triggered.connect(self.notification_changed_cb) self.settingsAction = QtWidgets.QAction( fromUTF8(escape(_("Edit Firewall Settings..."))), self) self.settingsAction.triggered.connect(self.configure_cb) self.changeZonesAction = QtWidgets.QAction( fromUTF8(escape(_("Change Zones of Connections..."))), self) self.changeZonesAction.triggered.connect(self.nm_connection_editor) self.shieldsAction = QtWidgets.QAction( fromUTF8(escape(_("Configure Shields UP/Down Zones..."))), self) self.shieldsAction.triggered.connect(self.configure_shields) self.panicAction = QtWidgets.QAction( fromUTF8(escape(_("Block all network traffic"))), self) self.panicAction.setCheckable(True) self.panicAction.setChecked(False) self.panicAction.triggered.connect(self.panic_mode_cb) self.aboutAction = QtWidgets.QAction(fromUTF8(escape(_("About"))), self) self.aboutAction.triggered.connect(self.about_dialog.exec_) #self.quitAction = QtWidgets.QAction(fromUTF8(escape(_("Quit"))), self, # triggered=self.quit) self.connectionsAction = QtWidgets.QWidgetAction(self) self.connectionsAction.setDefaultWidget(QtWidgets.QLabel( fromUTF8(""+escape(_("Connections"))+" "))) self.interfacesAction = QtWidgets.QWidgetAction(self) self.interfacesAction.setDefaultWidget(QtWidgets.QLabel( fromUTF8(""+escape(_("Interfaces"))+" "))) self.sourcesAction = QtWidgets.QWidgetAction(self) self.sourcesAction.setDefaultWidget(QtWidgets.QLabel( fromUTF8(""+escape(_("Sources"))+" "))) # init self.left_menu = QtWidgets.QMenu() self.left_menu.setStyleSheet('QMenu { margin: 5px; }') self.right_menu = QtWidgets.QMenu() self.right_menu.addAction(self.shieldsupAction) self.right_menu.addAction(self.notificationsAction) self.right_menu.addSeparator() self.right_menu.addAction(self.settingsAction) self.right_menu.addAction(self.changeZonesAction) self.right_menu.addAction(self.shieldsAction) self.right_menu.addSeparator() self.right_menu.addAction(self.panicAction) self.right_menu.addSeparator() self.right_menu.addAction(self.aboutAction) #self.right_menu.addSeparator() #self.right_menu.addAction(self.quitAction) self.setContextMenu(self.right_menu) self.activated.connect(self.activated_cb) self.set_mode("error") self.set_icon() self.setVisible(self.show_inactive) # init notification Notify.init(self.prog) # connect to firewalld DBusQtMainLoop(set_as_default=True) try: self.bus = slip.dbus.SystemBus() self.bus.default_timeout = None except Exception as msg: print("Not using slip", msg) self.bus = dbus.SystemBus() if nm_is_imported(): self.bus.add_signal_receiver( self.nm_signal_receiver, dbus_interface=nm_get_dbus_interface(), signal_name='PropertiesChanged', member_keyword='member') self.nm_signal_receiver() self.fw = FirewallClient(self.bus, wait=1) self.fw.setExceptionHandler(self._exception_handler) self.fw.connect("connection-established", self.connection_established) self.fw.connect("connection-lost", self.connection_lost) self.fw.connect("reloaded", self.reloaded), self.fw.connect("default-zone-changed", self.default_zone_changed) self.fw.connect("panic-mode-enabled", self.panic_mode_enabled) self.fw.connect("panic-mode-disabled", self.panic_mode_disabled) self.fw.connect("interface-added", self.interface_added) self.fw.connect("interface-removed", self.interface_removed) self.fw.connect("zone-of-interface-changed", self.zone_of_interface_changed) self.fw.connect("source-added", self.source_added) self.fw.connect("source-removed", self.source_removed) self.fw.connect("zone-of-source-changed", self.zone_of_source_changed) self.shields_editor = ShieldsEditor(self.fw, self.settings, None, None) self.load_settings() def _exception_handler(self, exception_message): if "NotAuthorizedException" in exception_message: self.error(fromUTF8(escape(_("Authorization failed.")))) elif "INVALID_NAME" in exception_message: msg = exception_message.replace("INVALID_NAME", _("Invalid name")) self.warning(fromUTF8(escape(msg))) elif "NAME_CONFLICT" in exception_message: msg = exception_message.replace("NAME_CONFLICT", _("Name already exists")) self.warning(fromUTF8(escape(msg))) elif "NO_DEFAULTS" in exception_message: pass else: self.error(fromUTF8(exception_message)) def quit(self): sys.exit(1) def set_icon(self, mode=None): if mode is not None: self.setIcon(self.icons[mode]) elif self.mode != "normal": self.setIcon(self.icons[self.mode]) elif self.default_zone == self.shields_up: self.setIcon(self.icons["normal-shields_up"]) else: self.setIcon(self.icons["normal-shields_down"]) def load_settings(self, name=None): self.settings.sync() notifications = self.settings.value("notifications", False, type=bool) self.notificationsAction.setChecked(notifications) self.show_inactive = self.settings.value("show-inactive", False, type=bool) self.blink = self.settings.value("blink", False, type=bool) self.blink_count = self.settings.value("blink-count", 5, type=int) self.shields_up = self.settings.value("shields-up", "block", type=str) if self.default_zone: self.shieldsupAction.setChecked( self.default_zone == self.shields_up) self.shields_editor.set_shields_up(self.shields_up) self.shields_down = self.settings.value("shields-down", "public", type=str) self.shields_editor.set_shields_down(self.shields_down) #print("shields-up=%s" % self.shields_up) #print("notifications=%s" % notifications) #print("blink=%s" % self.blink) #print("blink-count=%s" % self.blink_count) #print("show-inactive=%s" % self.show_inactive) if not self.fw.connected: self.setVisible(self.show_inactive) else: self.setVisible(True) def activated_cb(self, reason): if reason == QtWidgets.QSystemTrayIcon.Trigger: self.left_menu.popup(QtGui.QCursor.pos()) def update_active_zones(self): self.active_zones.clear() # remove all entries for the left menu self.left_menu.clear() # add connections entry self.left_menu.addAction(self.connectionsAction) if not self.fw.connected: return active_zones = self.fw.getActiveZones() if active_zones: self.active_zones = active_zones # get all active connections (NM) and interfaces connections = { } interfaces = { } sources = { } for zone in sorted(self.active_zones): if "interfaces" in self.active_zones[zone]: for interface in sorted(self.active_zones[zone]["interfaces"]): if interface not in self.connections: interfaces[interface] = zone if "sources" in self.active_zones[zone]: for source in sorted(self.active_zones[zone]["sources"]): sources[source] = zone # NM controlled connections for interface in self.connections: connection = self.connections[interface] if connection not in self.connections_name: connection_name = None else: connection_name = self.connections_name[connection] zone = nm_get_zone_of_connection(connection) connections[connection] = [ zone, connection_name ] binding = _("{entry} (Zone: {zone})") # add NM controlled bindings for connection in sorted(connections): zone = connections[connection][0] connection_name = connections[connection][1] if zone == "": _binding = _("{entry} (Default Zone: {default_zone})") action = QtWidgets.QAction( fromUTF8(escape( _binding.format(default_zone=self.default_zone, entry=connection_name))), self) else: action = QtWidgets.QAction( fromUTF8(escape(binding.format(zone=zone, entry=connection_name))), self) action.triggered.connect(functools.partial( self.zone_connection_editor, connection, connection_name, zone)) self.left_menu.addAction(action) # add interfaces entry self.left_menu.addAction(self.interfacesAction) # add other interfaces for interface in sorted(interfaces): zone = interfaces[interface] action = QtWidgets.QAction( fromUTF8(escape(binding.format(zone=zone, entry=interface))), self) action.triggered.connect(functools.partial( self.zone_interface_editor, interface, zone)) self.left_menu.addAction(action) # add interfaces entry self.left_menu.addAction(self.sourcesAction) for source in sorted(sources): zone = sources[source] action = QtWidgets.QAction( fromUTF8(escape(binding.format(zone=zone, entry=source))), self) action.triggered.connect(functools.partial( self.zone_source_editor, source, zone)) self.left_menu.addAction(action) def zone_interface_editor(self, interface, zone): if interface in self.zone_interface_editors: self.zone_interface_editors[interface].set_zone(zone) self.zone_interface_editors[interface].show() return self.zone_interface_editors[interface].raise_() editor = ZoneInterfaceEditor(self.fw, interface, zone) self.zone_interface_editors[interface] = editor editor.show() editor.raise_() editor.show() def zone_connection_editor(self, connection, connection_name, zone): if connection in self.zone_connection_editors: self.zone_connection_editors[connection].set_zone(zone) self.zone_connection_editors[connection].show() return self.zone_connection_editors[connection].raise_() editor = ZoneConnectionEditor(self.fw, connection, connection_name, zone) self.zone_connection_editors[connection] = editor editor.show() editor.raise_() editor.show() def zone_source_editor(self, source, zone): if source in self.zone_source_editors: self.zone_source_editors[source].set_zone(zone) self.zone_source_editors[source].show() return self.zone_source_editors[source].raise_() editor = ZoneSourceEditor(self.fw, source, zone) self.zone_source_editors[source] = editor editor.show() editor.raise_() editor.show() def nm_signal_receiver(self, *args, **kwargs): self.connections.clear() self.connections_name.clear() # do not use NMClient could result in python core dump if nm_is_imported(): text = _("Failed to get connections from NetworkManager") try: nm_get_connections(self.connections, self.connections_name) except Exception: self.notify(escape(text), urgency=Notify.Urgency.CRITICAL) if text not in self.tooltip_messages: self.tooltip_messages.append(text) else: if text in self.tooltip_messages: self.tooltip_messages.remove(text) else: text = _("No NetworkManager imports available") self.notify(escape(text), urgency=Notify.Urgency.CRITICAL) if text not in self.tooltip_messages: self.tooltip_messages.append(text) self.update_tooltip() def notify(self, msg, urgency="noicon", timeout=5): #self.showMessage(fromUTF8(escape(self.name)), msg, self.urgencies[urgency], timeout*1000) n = Notify.Notification.new(escape(self.name), msg, self.icon_name) n.set_urgency(Notify.Urgency.NORMAL) try: n.show() except: return def shieldsup_changed_cb(self): if self.shieldsupAction.isChecked(): zone = str(self.shields_up) else: zone = str(self.shields_down) if self.fw.connected and self.default_zone != zone: try: self.fw.setDefaultZone(zone) except dbus.exceptions.DBusException as e: print("Error: %s" % e.get_dbus_message()) def notification_changed_cb(self): self.settings.setValue("notifications", self.notificationsAction.isChecked()) self.settings.sync() def __blink(self, arg=None): if self._blink_count != 0: if self._blink_count > 0 and self._blink: self._blink_count -= 1 self._blink = not self._blink if not self.timer: self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.__blink) self.timer.setInterval(1000) self.timer.start() if not self._blink: self.set_icon() else: self.set_icon("normal") def get_mode(self): return self.mode def set_mode(self, mode): if self.mode != mode: if self.timer and self.timer.isActive(): self.timer.stop() self._blink = False self.mode = mode elif self.mode == mode and self.timer: if self._blink_count == 0: self._blink_count += 1 return if mode == "normal": self.set_icon() return if self.blink: if self.blink_count != 0: self._blink = True self._blink_count = self.blink_count self.__blink() else: self.set_icon() def update_tooltip(self): if self.get_mode() == "error": self.setToolTip(fromUTF8("" + \ _("No connection to firewall daemon") + \ "")) return messages = [ ] if self.panicAction.isChecked(): messages.append("" + \ _("All network traffic is blocked.") + \ "") if self.default_zone: messages.append(_("Default Zone: '%s'") % self.default_zone) for interface in self.connections: connection = self.connections[interface] zone = nm_get_zone_of_connection(connection) if zone == "": text = _("Default Zone '{default_zone}' active for connection " "'{connection}' on interface '{interface}'") else: text = _("Zone '{zone}' active for connection " "'{connection}' on interface '{interface}'") messages.append(text.format(zone=zone, default_zone=self.default_zone, connection=connection, interface=interface)) if len(self.active_zones) > 0: for zone in sorted(self.active_zones): if "interfaces" in self.active_zones[zone]: for interface in sorted(self.active_zones[zone]["interfaces"]): if interface not in self.connections: text = _("Zone '{zone}' active for interface " "'{interface}'") connection = None messages.append(text.format(zone=zone, connection=connection, interface=interface)) if "sources" in self.active_zones[zone]: for source in sorted(self.active_zones[zone]["sources"]): text = _("Zone '{zone}' active for source {source}") connection = None messages.append(text.format(zone=zone, source=source)) else: messages.append(_("No Active Zones.")) messages.extend(self.tooltip_messages) tooltip = ""+"
".join(messages)+"" self.setToolTip(fromUTF8(""+tooltip+"")) self.set_icon() def show(self): # do not automatically show the applet pass def panic_mode_cb(self): if not self.fw or not self.fw.connected: return if self.panicAction.isChecked(): self.fw.enablePanicMode() else: self.fw.disablePanicMode() self.panicAction.setChecked(not self.panicAction.isChecked()) def configure_shields(self): self.shields_editor.show() self.shields_editor.raise_() def nm_connection_editor(self, item, uuid=None): if NM_CONNECTION_EDITOR == "": self.warning("NetworkManager connection editor is missing.") return if uuid: if "kde-" in NM_CONNECTION_EDITOR: os.system("%s %s &" % (NM_CONNECTION_EDITOR, uuid)) else: os.system("%s --edit=%s &" % (NM_CONNECTION_EDITOR, uuid)) else: os.system("%s &" % NM_CONNECTION_EDITOR) def warning(self, text): QtWidgets.QMessageBox.warning(None, fromUTF8(escape(self.name)), text) def error(self, text): QtWidgets.QMessageBox.critical(None, fromUTF8(escape(self.name)), text) def configure_cb(self, widget): os.system("firewall-config &") # firewallClient signal receivers def connection_established(self, first=False): self.default_zone = self.fw.getDefaultZone() self.panicAction.setChecked(self.fw.queryPanicMode()) self.update_active_zones() self.shields_editor.zones_changed() if self.shields_up: self.shieldsupAction.setChecked( self.default_zone == self.shields_up) if self.notificationsAction.isChecked(): self.notify(escape(_("Connection to FirewallD established."))) self.setVisible(True) self.set_mode("normal") self.update_tooltip() def connection_lost(self): self.default_zone = None self.set_mode("error") self.update_active_zones() self.update_tooltip() self.panicAction.setChecked(False) if self.notificationsAction.isChecked(): self.notify(escape(_("Connection to FirewallD lost."))) self.setVisible(self.show_inactive) def reloaded(self): if self.notificationsAction.isChecked(): self.notify(escape(_("FirewallD has been reloaded."))) self.update_active_zones() self.update_tooltip() def default_zone_changed(self, zone): self.default_zone = zone if self.notificationsAction.isChecked(): self.notify(escape(_("Default zone changed to '%s'.") % zone)) if self.shields_up: self.shieldsupAction.setChecked( self.default_zone == self.shields_up) self.update_active_zones() self.update_tooltip() def _panic_mode(self, enable): self.panicAction.setChecked(enable) self.update_tooltip() if enable: self.set_mode("panic") else: self.set_mode("normal") if self.notificationsAction.isChecked(): ed = { 1: _("All network traffic is blocked."), 0: _("Network traffic is not blocked anymore.") } self.notify(escape(ed[enable])) def panic_mode_enabled(self): self._panic_mode(True) def panic_mode_disabled(self): self._panic_mode(False) def _interface(self, zone, interface, enable): self.update_active_zones() self.update_tooltip() # close dialog of removed interface if not enable: if interface in self.connections: connection = self.connections[interface] if connection in self.zone_connection_editors: self.zone_connection_editors[connection].hide() del self.zone_connection_editors[connection] elif interface in self.zone_interface_editors: self.zone_interface_editors[interface].hide() del self.zone_interface_editors[interface] # send notification if enabled if self.notificationsAction.isChecked(): ed = { 1: _("activated"), 0: _("deactivated") } if interface in self.connections: connection = self.connections[interface] zone = nm_get_zone_of_connection(connection) if zone == "": text = _("Default zone '{default_zone}' " "{activated_deactivated} for " "connection '{connection}' on " "interface '{interface}'") else: text = _("Zone '{zone}' {activated_deactivated} for " "connection '{connection}' on " "interface '{interface}'") else: connection = None text = _("Zone '{zone}' {activated_deactivated} for " "interface '{interface}'") self.notify(escape(text.format( zone=zone, default_zone=self.default_zone, activated_deactivated=ed[enable], connection=connection, interface=interface))) def interface_added(self, zone, interface): self._interface(zone, interface, True) def interface_removed(self, zone, interface): self._interface(zone, interface, False) def zone_of_interface_changed(self, zone, interface): # update zone editor if interface in self.zone_interface_editors: self.zone_interface_editors[interface].set_zone(zone) self.update_active_zones() self.update_tooltip() if self.notificationsAction.isChecked(): self.notify(escape(_("Zone '%s' activated for interface '%s'") % \ (zone, interface))) def _source(self, zone, source, enable): self.update_active_zones() self.update_tooltip() # close dialog of removed source if not enable: if source in self.zone_source_editors: self.zone_source_editors[source].hide() del self.zone_source_editors[source] # send notification if enabled if self.notificationsAction.isChecked(): ed = { 1: _("activated"), 0: _("deactivated") } text = _("Zone '{zone}' {activated_deactivated} for " "source '{source}'") self.notify(escape(text.format( zone=zone, activated_deactivated=ed[enable], source=source))) def source_added(self, zone, source): self._source(zone, source, True) def source_removed(self, zone, source): self._source(zone, source, False) def zone_of_source_changed(self, zone, source): index = source if source in self.zone_source_editors: self.zone_source_editors[source].set_zone(zone) # update zone editor if index in self.zone_interface_editors: self.zone_interface_editors[index].set_zone(zone) self.update_active_zones() self.update_tooltip() if self.notificationsAction.isChecked(): self.notify(escape(_("Zone '%s' activated for source '%s'") % \ (zone, source))) # MAIN ######################################################################## if len(sys.argv) > 1: print("""Usage: %s [options] Options: -h, --help show this help message and exit """ % sys.argv[0]) sys.exit(1) # reset SIGINT signal to default signal.signal(signal.SIGINT, signal.SIG_DFL) app = QtWidgets.QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) applet = TrayApplet() applet.show() sys.exit(app.exec_())