def __init__(self, parent=None): QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) # Other interesting flags: QtCore.Qt.Tool | QtCore.Qt.BypassWindowManagerHint self._cfg = Config.get() self.setupUi(self) self._width = self.width() self._height = self.height() dialog_geometry = self._cfg.getSettings("promptDialog/geometry") if dialog_geometry == QtCore.QByteArray: self.restoreGeometry(dialog_geometry) self.setWindowTitle("OpenSnitch v%s" % version) self._lock = threading.Lock() self._con = None self._rule = None self._local = True self._peer = None self._prompt_trigger.connect(self.on_connection_prompt_triggered) self._timeout_trigger.connect(self.on_timeout_triggered) self._tick_trigger.connect(self.on_tick_triggered) self._tick = int(self._cfg.getSettings( self._cfg.DEFAULT_TIMEOUT_KEY)) if self._cfg.hasKey( self._cfg.DEFAULT_TIMEOUT_KEY) else self.DEFAULT_TIMEOUT self._tick_thread = None self._done = threading.Event() self._timeout_text = "" self._timeout_triggered = False self._apps_parser = LinuxDesktopParser() self.denyButton.clicked.connect(self._on_deny_clicked) # also accept button self.applyButton.clicked.connect(self._on_apply_clicked) self._apply_text = QC.translate("popups", "Allow") self._deny_text = QC.translate("popups", "Deny") self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY) self.whatIPCombo.setVisible(False) self.checkDstIP.setVisible(False) self.checkDstPort.setVisible(False) self.checkUserID.setVisible(False) self.appDescriptionLabel.setVisible(False) self._ischeckAdvanceded = False self.checkAdvanced.toggled.connect(self._check_advanced_toggled) if QtGui.QIcon.hasThemeIcon("emblem-default") == False: self.applyButton.setIcon(self.style().standardIcon( getattr(QtWidgets.QStyle, "SP_DialogApplyButton"))) self.denyButton.setIcon(self.style().standardIcon( getattr(QtWidgets.QStyle, "SP_DialogCancelButton")))
def __init__(self, parent=None, appicon=None): super(ProcessDetailsDialog, self).__init__(parent) QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) self.setWindowFlags(QtCore.Qt.Window) self.setupUi(self) self.setWindowIcon(appicon) self._app_name = None self._app_icon = None self._apps_parser = LinuxDesktopParser() self._nodes = Nodes.instance() self._notification_callback.connect(self._cb_notification_callback) self._nid = None self._pid = "" self._notifications_sent = {} self.cmdClose.clicked.connect(self._cb_close_clicked) self.cmdAction.clicked.connect(self._cb_action_clicked) self.comboPids.currentIndexChanged.connect(self._cb_combo_pids_changed) self.TABS[self.TAB_STATUS]['text'] = self.textStatus self.TABS[self.TAB_DESCRIPTORS]['text'] = self.textOpenedFiles self.TABS[self.TAB_IOSTATS]['text'] = self.textIOStats self.TABS[self.TAB_MAPS]['text'] = self.textMappedFiles self.TABS[self.TAB_STACK]['text'] = self.textStack self.TABS[self.TAB_ENVS]['text'] = self.textEnv self.TABS[self.TAB_DESCRIPTORS]['text'].setFont( QtGui.QFont("monospace")) self.iconStart = QtGui.QIcon.fromTheme("media-playback-start") self.iconPause = QtGui.QIcon.fromTheme("media-playback-pause") if QtGui.QIcon.hasThemeIcon("window-close") == False: self.cmdClose.setIcon(self.style().standardIcon( getattr(QtWidgets.QStyle, "SP_DialogCloseButton"))) self.iconStart = self.style().standardIcon( getattr(QtWidgets.QStyle, "SP_MediaPlay")) self.iconPause = self.style().standardIcon( getattr(QtWidgets.QStyle, "SP_MediaPause"))
class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): LOG_TAG = "[ProcessDetails]: " _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply) TAB_STATUS = 0 TAB_DESCRIPTORS = 1 TAB_IOSTATS = 2 TAB_MAPS = 3 TAB_STACK = 4 TAB_ENVS = 5 TABS = { TAB_STATUS: { "text": None, "scrollPos": 0 }, TAB_DESCRIPTORS: { "text": None, "scrollPos": 0 }, TAB_IOSTATS: { "text": None, "scrollPos": 0 }, TAB_MAPS: { "text": None, "scrollPos": 0 }, TAB_STACK: { "text": None, "scrollPos": 0 }, TAB_ENVS: { "text": None, "scrollPos": 0 } } def __init__(self, parent=None): super(ProcessDetailsDialog, self).__init__(parent) QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) self.setWindowFlags(QtCore.Qt.Window) self.setupUi(self) self._app_name = None self._app_icon = None self._apps_parser = LinuxDesktopParser() self._nodes = Nodes.instance() self._notification_callback.connect(self._cb_notification_callback) self._nid = None self._pid = "" self._notifications_sent = {} self.cmdClose.clicked.connect(self._cb_close_clicked) self.cmdAction.clicked.connect(self._cb_action_clicked) self.comboPids.currentIndexChanged.connect(self._cb_combo_pids_changed) self.TABS[self.TAB_STATUS]['text'] = self.textStatus self.TABS[self.TAB_DESCRIPTORS]['text'] = self.textOpenedFiles self.TABS[self.TAB_IOSTATS]['text'] = self.textIOStats self.TABS[self.TAB_MAPS]['text'] = self.textMappedFiles self.TABS[self.TAB_STACK]['text'] = self.textStack self.TABS[self.TAB_ENVS]['text'] = self.textEnv self.TABS[self.TAB_DESCRIPTORS]['text'].setFont( QtGui.QFont("monospace")) self.iconStart = QtGui.QIcon.fromTheme("media-playback-start") self.iconPause = QtGui.QIcon.fromTheme("media-playback-pause") if QtGui.QIcon.hasThemeIcon("window-close") == False: self.cmdClose.setIcon(self.style().standardIcon( getattr(QtWidgets.QStyle, "SP_DialogCloseButton"))) self.iconStart = self.style().standardIcon( getattr(QtWidgets.QStyle, "SP_MediaPlay")) self.iconPause = self.style().standardIcon( getattr(QtWidgets.QStyle, "SP_MediaPause")) @QtCore.pyqtSlot(ui_pb2.NotificationReply) def _cb_notification_callback(self, reply): if reply.id in self._notifications_sent: noti = self._notifications_sent[reply.id] if reply.code == ui_pb2.ERROR: self._show_message( QtCore.QCoreApplication.translate( "proc_details", "<b>Error loading process information:</b> <br><br>\n\n" ) + reply.data) self._pid = "" self._set_button_running(False) # if we haven't loaded any data yet, just close the window if self._data_loaded == False: # but if there're more than 1 pid keep the window open. # we may have one pid already closed and one alive. if self.comboPids.count() <= 1: self._close() self._delete_notification(reply.id) return if noti.type == ui_pb2.MONITOR_PROCESS and reply.data != "": self._load_data(reply.data) elif noti.type == ui_pb2.STOP_MONITOR_PROCESS: if reply.data != "": self._show_message( QtCore.QCoreApplication.translate( "proc_details", "<b>Error stopping monitoring process:</b><br><br>" ) + reply.data) self._set_button_running(False) self._delete_notification(reply.id) else: print("[stats] unknown notification received: ", reply.id) def closeEvent(self, e): self._close() def _cb_close_clicked(self): self._close() def _cb_combo_pids_changed(self, idx): if idx == -1: return # TODO: this event causes to send to 2 Start notifications #if self._pid != "" and self._pid != self.comboPids.currentText(): # self._stop_monitoring() # self._pid = self.comboPids.currentText() # self._start_monitoring() def _cb_action_clicked(self): if not self.cmdAction.isChecked(): self._stop_monitoring() else: self._start_monitoring() def _show_message(self, text): Message.ok(text, "", QtWidgets.QMessageBox.Warning) def _delete_notification(self, nid): if nid in self._notifications_sent: del self._notifications_sent[nid] def _reset(self): self._app_name = None self._app_icon = None self.comboPids.clear() self.labelProcName.setText( QtCore.QCoreApplication.translate("proc_details", "loading...")) self.labelProcArgs.setText( QtCore.QCoreApplication.translate("proc_details", "loading...")) self.labelProcIcon.clear() self.labelStatm.setText("") self.labelCwd.setText("") for tidx in range(0, len(self.TABS)): self.TABS[tidx]['text'].setPlainText("") def _set_button_running(self, yes): if yes: self.cmdAction.setChecked(True) self.cmdAction.setIcon(self.iconPause) else: self.cmdAction.setChecked(False) self.cmdAction.setIcon(self.iconStart) def _close(self): self._stop_monitoring() self.comboPids.clear() self._pid = "" self.hide() def monitor(self, pids): if self._pid != "": self._stop_monitoring() self._data_loaded = False self._pids = pids self._reset() for pid in pids: self.comboPids.addItem(pid) self.show() self._start_monitoring() def _set_tab_text(self, tab_idx, text): self.TABS[tab_idx]['scrollPos'] = self.TABS[tab_idx][ 'text'].verticalScrollBar().value() self.TABS[tab_idx]['text'].setPlainText(text) self.TABS[tab_idx]['text'].verticalScrollBar().setValue( self.TABS[tab_idx]['scrollPos']) def _start_monitoring(self): try: # avoid to send notifications without a pid if self._pid != "": return self._pid = self.comboPids.currentText() if self._pid == "": return self._set_button_running(True) noti = ui_pb2.Notification(clientName="", serverName="", type=ui_pb2.MONITOR_PROCESS, data=self._pid, rules=[]) self._nid = self._nodes.send_notification( self._pids[self._pid], noti, self._notification_callback) self._notifications_sent[self._nid] = noti except Exception as e: print(self.LOG_TAG + "exception starting monitoring: ", e) def _stop_monitoring(self): if self._pid == "": return self._set_button_running(False) noti = ui_pb2.Notification(clientName="", serverName="", type=ui_pb2.STOP_MONITOR_PROCESS, data=str(self._pid), rules=[]) self._nid = self._nodes.send_notification(self._pids[self._pid], noti, self._notification_callback) self._notifications_sent[self._nid] = noti self._pid = "" self._app_icon = None def _load_data(self, data): tab_idx = self.tabWidget.currentIndex() try: proc = json.loads(data) self._load_app_icon(proc['Path']) if self._app_name != None: self.labelProcName.setText("<b>" + self._app_name + "</b>") self.labelProcName.setToolTip("<b>" + self._app_name + "</b>") #if proc['Path'] not in proc['Args']: # proc['Args'].insert(0, proc['Path']) self.labelProcArgs.setFixedHeight(30) self.labelProcArgs.setText(" ".join(proc['Args'])) self.labelProcArgs.setToolTip(" ".join(proc['Args'])) self.labelCwd.setText("<b>CWD: </b>" + proc['CWD']) self.labelCwd.setToolTip("<b>CWD: </b>" + proc['CWD']) self._load_mem_data(proc['Statm']) if tab_idx == self.TAB_STATUS: self._set_tab_text(tab_idx, proc['Status']) elif tab_idx == self.TAB_DESCRIPTORS: self._load_descriptors(proc['Descriptors']) elif tab_idx == self.TAB_IOSTATS: self._load_iostats(proc['IOStats']) elif tab_idx == self.TAB_MAPS: self._set_tab_text(tab_idx, proc['Maps']) elif tab_idx == self.TAB_STACK: self._set_tab_text(tab_idx, proc['Stack']) elif tab_idx == self.TAB_ENVS: self._load_env_vars(proc['Env']) self._data_loaded = True except Exception as e: print(self.LOG_TAG + "exception loading data: ", e) def _load_app_icon(self, proc_path): if self._app_icon != None: return self._app_name, self._app_icon, _, _ = self._apps_parser.get_info_by_path( proc_path, "terminal") icon = QtGui.QIcon().fromTheme(self._app_icon) pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48))) self.labelProcIcon.setPixmap(pixmap) if self._app_name == None: self._app_name = proc_path def _load_iostats(self, iostats): ioText = "%-16s %dMB<br>%-16s %dMB<br>%-16s %d<br>%-16s %d<br>%-16s %dMB<br>%-16s %dMB<br>" % ( "<b>Chars read:</b>", ((iostats['RChar'] / 1024) / 1024), "<b>Chars written:</b>", ((iostats['WChar'] / 1024) / 1024), "<b>Syscalls read:</b>", (iostats['SyscallRead']), "<b>Syscalls write:</b>", (iostats['SyscallWrite']), "<b>KB read:</b>", ((iostats['ReadBytes'] / 1024) / 1024), "<b>KB written: </b>", ((iostats['WriteBytes'] / 1024) / 1024)) self.textIOStats.setPlainText("") self.textIOStats.appendHtml(ioText) def _load_mem_data(self, mem): # assuming page size == 4096 pagesize = 4096 memText = "<b>VIRT:</b> %dMB, <b>RSS:</b> %dMB, <b>Libs:</b> %dMB, <b>Data:</b> %dMB, <b>Text:</b> %dMB" % ( ((mem['Size'] * pagesize) / 1024) / 1024, ((mem['Resident'] * pagesize) / 1024) / 1024, ((mem['Lib'] * pagesize) / 1024) / 1024, ((mem['Data'] * pagesize) / 1024) / 1024, ((mem['Text'] * pagesize) / 1024) / 1024) self.labelStatm.setText(memText) def _load_descriptors(self, descriptors): text = "%-12s%-40s%-8s -> %s\n\n" % ("Size", "Time", "Name", "Symlink") for d in descriptors: text += "{:<12}{:<40}{:<8} -> {}\n".format(str(d['Size']), d['ModTime'], d['Name'], d['SymLink']) self._set_tab_text(self.TAB_DESCRIPTORS, text) def _load_env_vars(self, envs): if envs == {}: self._set_tab_text(self.TAB_ENVS, "<no environment variables>") return text = "%-15s\t%s\n\n" % ("Name", "Value") for env_name in envs: text += "%-15s:\t%s\n" % (env_name, envs[env_name]) self._set_tab_text(self.TAB_ENVS, text)
class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): _prompt_trigger = QtCore.pyqtSignal() _tick_trigger = QtCore.pyqtSignal() _timeout_trigger = QtCore.pyqtSignal() DEFAULT_TIMEOUT = 15 ACTION_IDX_DENY = 0 ACTION_IDX_ALLOW = 1 FIELD_REGEX_HOST = "regex_host" FIELD_REGEX_IP = "regex_ip" FIELD_PROC_PATH = "process_path" FIELD_PROC_ARGS = "process_args" FIELD_PROC_ID = "process_id" FIELD_USER_ID = "user_id" FIELD_DST_IP = "dst_ip" FIELD_DST_PORT = "dst_port" FIELD_DST_NETWORK = "dst_network" FIELD_DST_HOST = "simple_host" # don't translate DURATION_30s = "30s" DURATION_5m = "5m" DURATION_15m = "15m" DURATION_30m = "30m" DURATION_1h = "1h" # don't translate # label displayed in the pop-up combo DURATION_session = QC.translate("popups", "until reboot") # label displayed in the pop-up combo DURATION_forever = QC.translate("popups", "forever") def __init__(self, parent=None, appicon=None): QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) # Other interesting flags: QtCore.Qt.Tool | QtCore.Qt.BypassWindowManagerHint self._cfg = Config.get() self.setupUi(self) self.setWindowIcon(appicon) self._width = None self._height = None dialog_geometry = self._cfg.getSettings("promptDialog/geometry") if dialog_geometry == QtCore.QByteArray: self.restoreGeometry(dialog_geometry) self.setWindowTitle("OpenSnitch v%s" % version) self._lock = threading.Lock() self._con = None self._rule = None self._local = True self._peer = None self._prompt_trigger.connect(self.on_connection_prompt_triggered) self._timeout_trigger.connect(self.on_timeout_triggered) self._tick_trigger.connect(self.on_tick_triggered) self._tick = int(self._cfg.getSettings( self._cfg.DEFAULT_TIMEOUT_KEY)) if self._cfg.hasKey( self._cfg.DEFAULT_TIMEOUT_KEY) else self.DEFAULT_TIMEOUT self._tick_thread = None self._done = threading.Event() self._timeout_text = "" self._timeout_triggered = False self._apps_parser = LinuxDesktopParser() self.denyButton.clicked.connect(self._on_deny_clicked) # also accept button self.applyButton.clicked.connect(self._on_apply_clicked) self._apply_text = QC.translate("popups", "Allow") self._deny_text = QC.translate("popups", "Deny") self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY) self.whatIPCombo.setVisible(False) self.checkDstIP.setVisible(False) self.checkDstPort.setVisible(False) self.checkUserID.setVisible(False) self.appDescriptionLabel.setVisible(False) self._ischeckAdvanceded = False self.checkAdvanced.toggled.connect(self._check_advanced_toggled) self.checkAdvanced.clicked.connect(self._button_clicked) self.durationCombo.activated.connect(self._button_clicked) self.whatCombo.activated.connect(self._button_clicked) self.whatIPCombo.activated.connect(self._button_clicked) self.checkDstIP.clicked.connect(self._button_clicked) self.checkDstPort.clicked.connect(self._button_clicked) self.checkUserID.clicked.connect(self._button_clicked) if QtGui.QIcon.hasThemeIcon("emblem-default"): return applyIcon = Icons.new("emblem-default") denyIcon = Icons.new("emblem-important") self.applyButton.setIcon(applyIcon) self.denyButton.setIcon(denyIcon) def showEvent(self, event): super(PromptDialog, self).showEvent(event) self.activateWindow() if self._width is None or self._height is None: self._width = self.width() self._height = self.height() self.setMinimumSize(self._width, self._height) self.setMaximumSize(self._width, self._height) self.move_popup() def move_popup(self): popup_pos = self._cfg.getInt(self._cfg.DEFAULT_POPUP_POSITION) point = QtWidgets.QDesktopWidget().availableGeometry() if popup_pos == self._cfg.POPUP_TOP_RIGHT: self.move(point.topRight()) elif popup_pos == self._cfg.POPUP_TOP_LEFT: self.move(point.topLeft()) elif popup_pos == self._cfg.POPUP_BOTTOM_RIGHT: self.move(point.bottomRight()) elif popup_pos == self._cfg.POPUP_BOTTOM_LEFT: self.move(point.bottomLeft()) def _stop_countdown(self): self.applyButton.setText("%s" % self._apply_text) self.denyButton.setText("%s" % self._deny_text) self._tick_thread.stop = True def _check_advanced_toggled(self, state): self.checkDstIP.setVisible(state) self.whatIPCombo.setVisible(state) self.destIPLabel.setVisible(not state) self.checkDstPort.setVisible(state) self.checkUserID.setVisible(state) self._ischeckAdvanceded = state def _button_clicked(self): self._stop_countdown() def _set_elide_text(self, widget, text, max_size=132): if len(text) > max_size: text = text[:max_size] + "..." widget.setText(text) def promptUser(self, connection, is_local, peer): # one at a time with self._lock: # reset state if self._tick_thread != None and self._tick_thread.is_alive(): self._tick_thread.join() self._cfg.reload() self._tick = int( self._cfg.getSettings(self._cfg.DEFAULT_TIMEOUT_KEY) ) if self._cfg.hasKey( self._cfg.DEFAULT_TIMEOUT_KEY) else self.DEFAULT_TIMEOUT self._tick_thread = threading.Thread(target=self._timeout_worker) self._tick_thread.stop = self._ischeckAdvanceded self._timeout_triggered = False self._rule = None self._local = is_local self._peer = peer self._con = connection self._done.clear() # trigger and show dialog self._prompt_trigger.emit() # start timeout thread self._tick_thread.start() # wait for user choice or timeout self._done.wait() return self._rule, self._timeout_triggered def _timeout_worker(self): if self._tick == 0: self._timeout_trigger.emit() return while self._tick > 0 and self._done.is_set() is False: t = threading.currentThread() # stop only stops the coundtdown, not the thread itself. if getattr(t, "stop", True): self._tick = int( self._cfg.getSettings(self._cfg.DEFAULT_TIMEOUT_KEY)) time.sleep(1) continue self._tick -= 1 self._tick_trigger.emit() time.sleep(1) if not self._done.is_set(): self._timeout_trigger.emit() @QtCore.pyqtSlot() def on_connection_prompt_triggered(self): self._render_connection(self._con) if self._tick > 0: self.show() @QtCore.pyqtSlot() def on_tick_triggered(self): self._set_cmd_action_text() @QtCore.pyqtSlot() def on_timeout_triggered(self): self._timeout_triggered = True self._send_rule() def _configure_default_duration(self): if self._cfg.hasKey(self._cfg.DEFAULT_DURATION_KEY): cur_idx = self._cfg.getInt(self._cfg.DEFAULT_DURATION_KEY) self.durationCombo.setCurrentIndex(cur_idx) else: self.durationCombo.setCurrentIndex(self._cfg.DEFAULT_DURATION_IDX) def _set_cmd_action_text(self): if self._cfg.getInt( self._cfg.DEFAULT_ACTION_KEY) == self.ACTION_IDX_ALLOW: self.applyButton.setText("%s (%d)" % (self._apply_text, self._tick)) self.denyButton.setText(self._deny_text) else: self.denyButton.setText("%s (%d)" % (self._deny_text, self._tick)) self.applyButton.setText(self._apply_text) def _set_app_description(self, description): if description != None and description != "": self.appDescriptionLabel.setVisible(True) self.appDescriptionLabel.setFixedHeight(50) self.appDescriptionLabel.setToolTip(description) self._set_elide_text(self.appDescriptionLabel, "%s" % description) else: self.appDescriptionLabel.setVisible(False) self.appDescriptionLabel.setFixedHeight(0) self.appDescriptionLabel.setText("") def _set_app_path(self, app_name, app_args, con): # show the binary path if it's not part of the cmdline args: # cmdline: telnet 1.1.1.1 (path: /usr/bin/telnet.netkit) # cmdline: /usr/bin/telnet.netkit 1.1.1.1 (the binary path is part of the cmdline args, no need to display it) if con.process_path != "" and len( con.process_args ) >= 1 and con.process_path not in con.process_args: self.appPathLabel.setToolTip("Process path: %s" % con.process_path) if app_name.lower() == app_args: self._set_elide_text(self.appPathLabel, "%s" % con.process_path) else: self._set_elide_text(self.appPathLabel, "(%s)" % con.process_path) self.appPathLabel.setVisible(True) else: self.appPathLabel.setVisible(False) self.appPathLabel.setText("") def _set_app_args(self, app_name, app_args): # if the app name and the args are the same, there's no need to display # the args label (amule for example) if app_name.lower() != app_args: self.argsLabel.setVisible(True) self._set_elide_text(self.argsLabel, app_args) self.argsLabel.setToolTip(app_args) else: self.argsLabel.setVisible(False) self.argsLabel.setText("") def _render_connection(self, con): app_name, app_icon, description, _ = self._apps_parser.get_info_by_path( con.process_path, "terminal") app_args = " ".join(con.process_args) self._set_app_description(description) self._set_app_path(app_name, app_args, con) self._set_app_args(app_name, app_args) if app_name == "": self.appPathLabel.setVisible(False) self.argsLabel.setVisible(False) app_name = QC.translate("popups", "Unknown process %s" % con.process_path) self.appNameLabel.setText( QC.translate("popups", "Outgoing connection")) else: self._set_elide_text(self.appNameLabel, "%s" % app_name, max_size=42) self.appNameLabel.setToolTip(app_name) self.cwdLabel.setToolTip("%s %s" % (QC.translate( "popups", "Process launched from:"), con.process_cwd)) self._set_elide_text(self.cwdLabel, con.process_cwd, max_size=32) pixmap = self._get_app_icon(app_icon) self.iconLabel.setPixmap(pixmap) message = self._get_popup_message(app_name, con) self.messageLabel.setText(message) self.messageLabel.setToolTip(message) self.sourceIPLabel.setText(con.src_ip) self.destIPLabel.setText(con.dst_ip) self.destPortLabel.setText(str(con.dst_port)) if self._local: try: uid = "%d (%s)" % (con.user_id, pwd.getpwuid( con.user_id).pw_name) except: uid = "" else: uid = "%d" % con.user_id self.uidLabel.setText(uid) self.pidLabel.setText("%s" % con.process_id) self.whatCombo.clear() self.whatIPCombo.clear() # the order of these combobox entries must match those in the preferences dialog # prefs -> UI -> Default target self.whatCombo.addItem(QC.translate("popups", "from this executable"), self.FIELD_PROC_PATH) if int(con.process_id) < 0: self.whatCombo.model().item(0).setEnabled(False) self.whatCombo.addItem( QC.translate("popups", "from this command line"), self.FIELD_PROC_ARGS) self.whatCombo.addItem( QC.translate("popups", "to port {0}").format(con.dst_port), self.FIELD_DST_PORT) self.whatCombo.addItem( QC.translate("popups", "to {0}").format(con.dst_ip), self.FIELD_DST_IP) self.whatCombo.addItem( QC.translate("popups", "from user {0}").format(uid), self.FIELD_USER_ID) if int(con.user_id) < 0: self.whatCombo.model().item(4).setEnabled(False) self.whatCombo.addItem(QC.translate("popups", "from this PID"), self.FIELD_PROC_ID) self._add_dst_networks_to_combo(self.whatCombo, con.dst_ip) if con.dst_host != "" and con.dst_host != con.dst_ip: self._add_dsthost_to_combo(con.dst_host) self.whatIPCombo.addItem( QC.translate("popups", "to {0}").format(con.dst_ip), self.FIELD_DST_IP) parts = con.dst_ip.split('.') nparts = len(parts) for i in range(1, nparts): self.whatCombo.addItem( QC.translate("popups", "to {0}.*").format('.'.join(parts[:i])), self.FIELD_REGEX_IP) self.whatIPCombo.addItem( QC.translate("popups", "to {0}.*").format('.'.join(parts[:i])), self.FIELD_REGEX_IP) self._add_dst_networks_to_combo(self.whatIPCombo, con.dst_ip) self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY) self._configure_default_duration() if int(con.process_id) > 0: self.whatCombo.setCurrentIndex( int(self._cfg.getSettings(self._cfg.DEFAULT_TARGET_KEY))) else: self.whatCombo.setCurrentIndex(2) self.checkDstIP.setChecked( self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_DSTIP)) self.checkDstPort.setChecked( self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_DSTPORT)) self.checkUserID.setChecked( self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_UID)) if self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED): self.checkAdvanced.toggle() self._set_cmd_action_text() self.checkAdvanced.setFocus() self.setFixedSize(self.size()) # https://gis.stackexchange.com/questions/86398/how-to-disable-the-escape-key-for-a-dialog def keyPressEvent(self, event): if not event.key() == QtCore.Qt.Key_Escape: super(PromptDialog, self).keyPressEvent(event) # prevent a click on the window's x # from quitting the whole application def closeEvent(self, e): self._send_rule() e.ignore() def _add_dst_networks_to_combo(self, combo, dst_ip): if type(ipaddress.ip_address(dst_ip)) == ipaddress.IPv4Address: combo.addItem( QC.translate("popups", "to {0}").format( ipaddress.ip_network(dst_ip + "/24", strict=False)), self.FIELD_DST_NETWORK) combo.addItem( QC.translate("popups", "to {0}").format( ipaddress.ip_network(dst_ip + "/16", strict=False)), self.FIELD_DST_NETWORK) combo.addItem( QC.translate("popups", "to {0}").format( ipaddress.ip_network(dst_ip + "/8", strict=False)), self.FIELD_DST_NETWORK) else: combo.addItem( QC.translate("popups", "to {0}").format( ipaddress.ip_network(dst_ip + "/64", strict=False)), self.FIELD_DST_NETWORK) combo.addItem( QC.translate("popups", "to {0}").format( ipaddress.ip_network(dst_ip + "/128", strict=False)), self.FIELD_DST_NETWORK) def _add_dsthost_to_combo(self, dst_host): self.whatCombo.addItem("%s" % dst_host, self.FIELD_DST_HOST) self.whatIPCombo.addItem("%s" % dst_host, self.FIELD_DST_HOST) parts = dst_host.split('.')[1:] nparts = len(parts) for i in range(0, nparts - 1): self.whatCombo.addItem( QC.translate("popups", "to *.{0}").format('.'.join(parts[i:])), self.FIELD_REGEX_HOST) self.whatIPCombo.addItem( QC.translate("popups", "to *.{0}").format('.'.join(parts[i:])), self.FIELD_REGEX_HOST) def _get_app_icon(self, app_icon): """we try to get the icon of an app from the system. If it's not found, then we'll try to search for it in common directories of the system. """ try: icon = QtGui.QIcon().fromTheme(app_icon) pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48))) if QtGui.QIcon().hasThemeIcon( app_icon) == False or pixmap.height() == 0: # sometimes the icon is an absolute path, sometimes it's not if os.path.isabs(app_icon): icon = QtGui.QIcon(app_icon) pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48))) else: icon_path = self._apps_parser.discover_app_icon(app_icon) if icon_path != None: icon = QtGui.QIcon(icon_path) pixmap = icon.pixmap( icon.actualSize(QtCore.QSize(48, 48))) except Exception as e: print("Exception _get_app_icon():", e) return pixmap def _get_popup_message(self, app_name, con): """ _get_popup_message helps constructing the message that is displayed on the pop-up dialog. Example: curl is connecting to www.opensnitch.io on TCP port 443 """ message = "<b>%s</b>" % app_name if not self._local: message = QC.translate("popups", "<b>Remote</b> process %s running on <b>%s</b>") % ( \ message, self._peer.split(':')[1]) msg_action = QC.translate("popups", "is connecting to <b>%s</b> on %s port %d") % ( \ con.dst_host or con.dst_ip, con.protocol.upper(), con.dst_port ) if con.dst_port == 53 and con.dst_ip != con.dst_host and con.dst_host != "": msg_action = QC.translate("popups", "is attempting to resolve <b>%s</b> via %s, %s port %d") % ( \ con.dst_host, con.dst_ip, con.protocol.upper(), con.dst_port) return "%s %s" % (message, msg_action) def _get_duration(self, duration_idx): if duration_idx == 0: return Config.DURATION_ONCE elif duration_idx == 1: return self.DURATION_30s elif duration_idx == 2: return self.DURATION_5m elif duration_idx == 3: return self.DURATION_15m elif duration_idx == 4: return self.DURATION_30m elif duration_idx == 5: return self.DURATION_1h elif duration_idx == 6: return Config.DURATION_UNTIL_RESTART else: return Config.DURATION_ALWAYS def _get_combo_operator(self, combo, what_idx): if combo.itemData(what_idx) == self.FIELD_PROC_PATH: return Config.RULE_TYPE_SIMPLE, "process.path", self._con.process_path elif combo.itemData(what_idx) == self.FIELD_PROC_ARGS: # this should not happen if len(self._con.process_args ) == 0 or self._con.process_args[0] == "": return Config.RULE_TYPE_SIMPLE, "process.path", self._con.process_path return Config.RULE_TYPE_SIMPLE, "process.command", ' '.join( self._con.process_args) elif combo.itemData(what_idx) == self.FIELD_PROC_ID: return Config.RULE_TYPE_SIMPLE, "process.id", "{0}".format( self._con.process_id) elif combo.itemData(what_idx) == self.FIELD_USER_ID: return Config.RULE_TYPE_SIMPLE, "user.id", "%s" % self._con.user_id elif combo.itemData(what_idx) == self.FIELD_DST_PORT: return Config.RULE_TYPE_SIMPLE, "dest.port", "%s" % self._con.dst_port elif combo.itemData(what_idx) == self.FIELD_DST_IP: return Config.RULE_TYPE_SIMPLE, "dest.ip", self._con.dst_ip elif combo.itemData(what_idx) == self.FIELD_DST_HOST: return Config.RULE_TYPE_SIMPLE, "dest.host", combo.currentText() elif combo.itemData(what_idx) == self.FIELD_DST_NETWORK: # strip "to ": "to x.x.x/20" -> "x.x.x/20" # we assume that to is one word in all languages parts = combo.currentText().split(' ') text = parts[len(parts) - 1] return Config.RULE_TYPE_NETWORK, "dest.network", text elif combo.itemData(what_idx) == self.FIELD_REGEX_HOST: parts = combo.currentText().split(' ') text = parts[len(parts) - 1] # ^(|.*\.)yahoo\.com dsthost = '\.'.join(text.split('.')).replace("*", "") dsthost = "^(|.*\.)%s" % dsthost[2:] return Config.RULE_TYPE_REGEXP, "dest.host", dsthost elif combo.itemData(what_idx) == self.FIELD_REGEX_IP: parts = combo.currentText().split(' ') text = parts[len(parts) - 1] return Config.RULE_TYPE_REGEXP, "dest.ip", "%s" % '\.'.join( text.split('.')).replace("*", ".*") def _on_deny_clicked(self): self._default_action = self.ACTION_IDX_DENY self._send_rule() def _on_apply_clicked(self): self._default_action = self.ACTION_IDX_ALLOW self._send_rule() def _is_list_rule(self): return self.checkUserID.isChecked() or self.checkDstPort.isChecked( ) or self.checkDstIP.isChecked() def _get_rule_name(self, rule): rule_temp_name = slugify("%s %s" % (rule.action, rule.duration)) if self._is_list_rule(): rule_temp_name = "%s-list" % rule_temp_name else: rule_temp_name = "%s-simple" % rule_temp_name rule_temp_name = slugify("%s %s" % (rule_temp_name, rule.operator.data)) return rule_temp_name[:128] def _send_rule(self): try: self._cfg.setSettings("promptDialog/geometry", self.saveGeometry()) self._rule = ui_pb2.Rule(name="user.choice") self._rule.enabled = True self._rule.action = Config.ACTION_DENY if self._default_action == self.ACTION_IDX_DENY else Config.ACTION_ALLOW self._rule.duration = self._get_duration( self.durationCombo.currentIndex()) what_idx = self.whatCombo.currentIndex() self._rule.operator.type, self._rule.operator.operand, self._rule.operator.data = self._get_combo_operator( self.whatCombo, what_idx) if self._rule.operator.data == "": print("Invalid rule, discarding: ", self._rule) self._rule = None return rule_temp_name = self._get_rule_name(self._rule) self._rule.name = rule_temp_name # TODO: move to a method data = [] if self.checkDstIP.isChecked( ) and self.whatCombo.itemData(what_idx) != self.FIELD_DST_IP: _type, _operand, _data = self._get_combo_operator( self.whatIPCombo, self.whatIPCombo.currentIndex()) data.append({ "type": _type, "operand": _operand, "data": _data }) rule_temp_name = slugify("%s %s" % (rule_temp_name, _data)) if self.checkDstPort.isChecked( ) and self.whatCombo.itemData(what_idx) != self.FIELD_DST_PORT: data.append({ "type": Config.RULE_TYPE_SIMPLE, "operand": "dest.port", "data": str(self._con.dst_port) }) rule_temp_name = slugify( "%s %s" % (rule_temp_name, str(self._con.dst_port))) if self.checkUserID.isChecked( ) and self.whatCombo.itemData(what_idx) != self.FIELD_USER_ID: data.append({ "type": Config.RULE_TYPE_SIMPLE, "operand": "user.id", "data": str(self._con.user_id) }) rule_temp_name = slugify( "%s %s" % (rule_temp_name, str(self._con.user_id))) if self._is_list_rule(): data.append({ "type": self._rule.operator.type, "operand": self._rule.operator.operand, "data": self._rule.operator.data }) self._rule.operator.data = json.dumps(data) self._rule.operator.type = Config.RULE_TYPE_LIST self._rule.operator.operand = Config.RULE_TYPE_LIST self._rule.name = rule_temp_name self.hide() if self._ischeckAdvanceded: self.checkAdvanced.toggle() self._ischeckAdvanceded = False except Exception as e: print("[pop-up] exception creating a rule:", e) finally: # signal that the user took a decision and # a new rule is available self._done.set() self.hide()
def __init__(self, parent=None, appicon=None): QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) # Other interesting flags: QtCore.Qt.Tool | QtCore.Qt.BypassWindowManagerHint self._cfg = Config.get() self.setupUi(self) self.setWindowIcon(appicon) self._width = None self._height = None dialog_geometry = self._cfg.getSettings("promptDialog/geometry") if dialog_geometry == QtCore.QByteArray: self.restoreGeometry(dialog_geometry) self.setWindowTitle("OpenSnitch v%s" % version) self._lock = threading.Lock() self._con = None self._rule = None self._local = True self._peer = None self._prompt_trigger.connect(self.on_connection_prompt_triggered) self._timeout_trigger.connect(self.on_timeout_triggered) self._tick_trigger.connect(self.on_tick_triggered) self._tick = int(self._cfg.getSettings( self._cfg.DEFAULT_TIMEOUT_KEY)) if self._cfg.hasKey( self._cfg.DEFAULT_TIMEOUT_KEY) else self.DEFAULT_TIMEOUT self._tick_thread = None self._done = threading.Event() self._timeout_text = "" self._timeout_triggered = False self._apps_parser = LinuxDesktopParser() self.whatIPCombo.setVisible(False) self.checkDstIP.setVisible(False) self.checkDstPort.setVisible(False) self.checkUserID.setVisible(False) self.appDescriptionLabel.setVisible(False) self._ischeckAdvanceded = False self.checkAdvanced.toggled.connect(self._check_advanced_toggled) self.checkAdvanced.clicked.connect(self._button_clicked) self.durationCombo.activated.connect(self._button_clicked) self.whatCombo.activated.connect(self._button_clicked) self.whatIPCombo.activated.connect(self._button_clicked) self.checkDstIP.clicked.connect(self._button_clicked) self.checkDstPort.clicked.connect(self._button_clicked) self.checkUserID.clicked.connect(self._button_clicked) self.allowIcon = Icons.new("emblem-default") denyIcon = Icons.new("emblem-important") rejectIcon = Icons.new("window-close") self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY) self.allowButton.clicked.connect( lambda: self._on_action_clicked(Config.ACTION_ALLOW_IDX)) self.allowButton.setIcon(self.allowIcon) self._allow_text = QC.translate("popups", "Allow") self._action_text = [ QC.translate("popups", "Deny"), QC.translate("popups", "Allow"), QC.translate("popups", "Reject") ] self._action_icon = [denyIcon, self.allowIcon, rejectIcon] m = QtWidgets.QMenu() m.addAction( denyIcon, self._action_text[Config.ACTION_DENY_IDX]).triggered.connect( lambda: self._on_action_clicked(Config.ACTION_DENY_IDX)) m.addAction( self.allowIcon, self._action_text[Config.ACTION_ALLOW_IDX]).triggered.connect( lambda: self._on_action_clicked(Config.ACTION_ALLOW_IDX)) m.addAction( rejectIcon, self._action_text[Config.ACTION_REJECT_IDX]).triggered.connect( lambda: self._on_action_clicked(Config.ACTION_REJECT_IDX)) self.actionButton.setMenu(m) self.actionButton.setText(self._action_text[Config.ACTION_DENY_IDX]) self.actionButton.setIcon(self._action_icon[Config.ACTION_DENY_IDX]) if self._default_action != Config.ACTION_ALLOW_IDX: self.actionButton.setText(self._action_text[self._default_action]) self.actionButton.setIcon(self._action_icon[self._default_action]) self.actionButton.clicked.connect(self._on_deny_btn_clicked)