Ejemplo n.º 1
0
    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)

        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)) if self._cfg.hasKey(
                self.CFG_DEFAULT_TIMEOUT) 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 = "Allow"
        self._deny_text = "Deny"
        self._default_action = self._cfg.getSettings(self.CFG_DEFAULT_ACTION)

        self.whatIPCombo.setVisible(False)
        self.checkDstIP.setVisible(False)
        self.checkDstPort.setVisible(False)
        self.checkUserID.setVisible(False)

        self._ischeckAdvanceded = False
        self.checkAdvanced.toggled.connect(self._checkbox_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")))
Ejemplo n.º 2
0
    def __init__(self, parent=None):
        QtWidgets.QDialog.__init__(self, parent,
                                   QtCore.Qt.WindowStaysOnTopHint)

        self.setupUi(self)

        self.setWindowTitle("opensnitch-qt 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 = PromptDialog.TIMEOUT
        self._tick_thread = None
        self._done = threading.Event()

        self._apps_parser = LinuxDesktopParser()

        self._app_name_label = self.findChild(QtWidgets.QLabel, "appNameLabel")
        self._app_icon_label = self.findChild(QtWidgets.QLabel, "iconLabel")
        self._message_label = self.findChild(QtWidgets.QLabel, "messageLabel")

        self._src_ip_label = self.findChild(QtWidgets.QLabel, "sourceIPLabel")
        self._dst_ip_label = self.findChild(QtWidgets.QLabel, "destIPLabel")
        self._dst_port_label = self.findChild(QtWidgets.QLabel,
                                              "destPortLabel")
        self._dst_host_label = self.findChild(QtWidgets.QLabel,
                                              "destHostLabel")
        self._uid_label = self.findChild(QtWidgets.QLabel, "uidLabel")
        self._pid_label = self.findChild(QtWidgets.QLabel, "pidLabel")
        self._args_label = self.findChild(QtWidgets.QLabel, "argsLabel")

        self._apply_button = self.findChild(QtWidgets.QPushButton,
                                            "applyButton")
        self._apply_button.clicked.connect(self._on_apply_clicked)

        self._action_combo = self.findChild(QtWidgets.QComboBox, "actionCombo")
        self._what_combo = self.findChild(QtWidgets.QComboBox, "whatCombo")
        self._duration_combo = self.findChild(QtWidgets.QComboBox,
                                              "durationCombo")
Ejemplo n.º 3
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"))
Ejemplo n.º 4
0
class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
    _prompt_trigger = QtCore.pyqtSignal()
    _tick_trigger = QtCore.pyqtSignal()
    _timeout_trigger = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        QtWidgets.QDialog.__init__(self, parent,
                                   QtCore.Qt.WindowStaysOnTopHint)

        self.setupUi(self)

        self.setWindowTitle("OpenSnitch v%s" % version)

        self._cfg = Config.get()
        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 = self._cfg.default_timeout
        self._tick_thread = None
        self._done = threading.Event()

        self._apps_parser = LinuxDesktopParser()

        self._app_name_label = self.findChild(QtWidgets.QLabel, "appNameLabel")
        self._app_icon_label = self.findChild(QtWidgets.QLabel, "iconLabel")
        self._message_label = self.findChild(QtWidgets.QLabel, "messageLabel")

        self._src_ip_label = self.findChild(QtWidgets.QLabel, "sourceIPLabel")
        self._dst_ip_label = self.findChild(QtWidgets.QLabel, "destIPLabel")
        self._uid_label = self.findChild(QtWidgets.QLabel, "uidLabel")
        self._pid_label = self.findChild(QtWidgets.QLabel, "pidLabel")
        self._args_label = self.findChild(QtWidgets.QLabel, "argsLabel")

        self._apply_button = self.findChild(QtWidgets.QPushButton,
                                            "applyButton")
        self._apply_button.clicked.connect(self._on_apply_clicked)

        self._action_combo = self.findChild(QtWidgets.QComboBox, "actionCombo")
        self._what_combo = self.findChild(QtWidgets.QComboBox, "whatCombo")
        self._duration_combo = self.findChild(QtWidgets.QComboBox,
                                              "durationCombo")

    def promptUser(self, connection, is_local, peer):
        # one at a time
        with self._lock:
            # reset state
            self._tick = self._cfg.default_timeout
            self._tick_thread = threading.Thread(target=self._timeout_worker)
            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

    def _timeout_worker(self):
        while self._tick > 0 and self._done.is_set() is False:
            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)
        self.show()

    @QtCore.pyqtSlot()
    def on_tick_triggered(self):
        self._apply_button.setText("Apply (%d)" % self._tick)

    @QtCore.pyqtSlot()
    def on_timeout_triggered(self):
        self._on_apply_clicked()

    def _render_connection(self, con):
        if self._local:
            app_name, app_icon, _ = self._apps_parser.get_info_by_path(
                con.process_path, "terminal")
        else:
            app_name, app_icon = "", "terminal"

        if app_name == "":
            self._app_name_label.setText(con.process_path)
        else:
            self._app_name_label.setText(app_name)

        icon = QtGui.QIcon().fromTheme(app_icon)
        pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
        self._app_icon_label.setPixmap(pixmap)

        if self._local:
            message = "<b>%s</b> is connecting to <b>%s</b> on %s port %d" % ( \
                        app_name,
                        con.dst_host or con.dst_ip,
                        con.protocol,
                        con.dst_port )
        else:
            message = "The process <b>%s</b> running on the computer <b>%s</b> is connecting to <b>%s</b> on %s port %d" % ( \
                        app_name,
                        self._peer.split(':')[1],
                        con.dst_host or con.dst_ip,
                        con.protocol,
                        con.dst_port )

        self._message_label.setText(message)

        self._src_ip_label.setText(con.src_ip)
        self._dst_ip_label.setText(con.dst_ip)

        if self._local:
            uid = "%d (%s)" % (con.user_id, pwd.getpwuid(con.user_id).pw_name)
        else:
            uid = "%d" % con.user_id

        self._uid_label.setText(uid)
        self._pid_label.setText("%s" % con.process_id)
        self._args_label.setText(' '.join(con.process_args))

        self._what_combo.clear()
        self._what_combo.addItem("from this process")
        self._what_combo.addItem("from user %d" % con.user_id)
        self._what_combo.addItem("to port %d" % con.dst_port)
        self._what_combo.addItem("to %s" % con.dst_ip)
        if con.dst_host != "":
            self._what_combo.addItem("to %s" % con.dst_host)
            parts = con.dst_host.split('.')[1:]
            nparts = len(parts)
            for i in range(0, nparts - 1):
                self._what_combo.addItem("to *.%s" % '.'.join(parts[i:]))

        if self._cfg.default_action == "allow":
            self._action_combo.setCurrentIndex(0)
        else:
            self._action_combo.setCurrentIndex(1)

        if self._cfg.default_duration == "once":
            self._duration_combo.setCurrentIndex(0)
        elif self._cfg.default_duration == "until restart":
            self._duration_combo.setCurrentIndex(1)
        else:
            self._duration_combo.setCurrentIndex(2)

        self._what_combo.setCurrentIndex(0)

        self._apply_button.setText("Apply (%d)" % self._tick)

        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._on_apply_clicked()
        e.ignore()

    def _on_apply_clicked(self):
        self._rule = ui_pb2.Rule(name="user.choice")

        action_idx = self._action_combo.currentIndex()
        if action_idx == 0:
            self._rule.action = "allow"
        else:
            self._rule.action = "deny"

        duration_idx = self._duration_combo.currentIndex()
        if duration_idx == 0:
            self._rule.duration = "once"
        elif duration_idx == 1:
            self._rule.duration = "until restart"
        else:
            self._rule.duration = "always"

        what_idx = self._what_combo.currentIndex()
        if what_idx == 0:
            self._rule.operator.type = "simple"
            self._rule.operator.operand = "process.path"
            self._rule.operator.data = self._con.process_path

        elif what_idx == 1:
            self._rule.operator.type = "simple"
            self._rule.operator.operand = "user.id"
            self._rule.operator.data = "%s" % self._con.user_id

        elif what_idx == 2:
            self._rule.operator.type = "simple"
            self._rule.operator.operand = "dest.port"
            self._rule.operator.data = "%s" % self._con.dst_port

        elif what_idx == 3:
            self._rule.operator.type = "simple"
            self._rule.operator.operand = "dest.ip"
            self._rule.operator.data = self._con.dst_ip

        elif what_idx == 4:
            self._rule.operator.type = "simple"
            self._rule.operator.operand = "dest.host"
            self._rule.operator.data = self._con.dst_host

        else:
            self._rule.operator.type = "regexp"
            self._rule.operator.operand = "dest.host"
            self._rule.operator.data = ".*\.%s" % '\.'.join(
                self._con.dst_host.split('.')[what_idx - 4:])

        self._rule.name = slugify("%s %s %s" %
                                  (self._rule.action, self._rule.operator.type,
                                   self._rule.operator.data))

        self.hide()
        # signal that the user took a decision and
        # a new rule is available
        self._done.set()
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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_ALLOW = "allow"
    ACTION_DENY = "deny"

    FIELD_REGEX_HOST = "regex_host"
    FIELD_REGEX_IP = "regex_ip"
    FIELD_PROC_PATH = "process_path"
    FIELD_PROC_ARGS = "process_args"
    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"

    DURATION_once = "once"
    DURATION_30s = "30s"
    DURATION_5m = "5m"
    DURATION_15m = "15m"
    DURATION_30m = "30m"
    DURATION_1h = "1h"
    # label displayed in the pop-up combo
    DURATION_session = "for this session"
    # field of a rule
    DURATION_restart = "until restart"
    # label displayed in the pop-up combo
    DURATION_forever = "forever"
    # field of a rule
    DURATION_always = "always"

    CFG_DEFAULT_TIMEOUT = "global/default_timeout"
    CFG_DEFAULT_ACTION = "global/default_action"

    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)

        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)) if self._cfg.hasKey(
                self.CFG_DEFAULT_TIMEOUT) 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 = "Allow"
        self._deny_text = "Deny"
        self._default_action = self._cfg.getSettings(self.CFG_DEFAULT_ACTION)

        self.whatIPCombo.setVisible(False)
        self.checkDstIP.setVisible(False)
        self.checkDstPort.setVisible(False)
        self.checkUserID.setVisible(False)

        self._ischeckAdvanceded = False
        self.checkAdvanced.toggled.connect(self._checkbox_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 showEvent(self, event):
        super(PromptDialog, self).showEvent(event)
        self.resize(540, 300)
        self.activateWindow()

    def _checkbox_toggled(self, state):
        self.applyButton.setText("%s" % self._apply_text)
        self.denyButton.setText("%s" % self._deny_text)
        self._tick_thread.stop = 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 _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)) if self._cfg.hasKey(
                    self.CFG_DEFAULT_TIMEOUT) 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))
                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):
        if self._cfg.getSettings(self.CFG_DEFAULT_ACTION) == self.ACTION_ALLOW:
            self._timeout_text = "%s (%d)" % (self._apply_text, self._tick)
            self.applyButton.setText(self._timeout_text)
        else:
            self._timeout_text = "%s (%d)" % (self._deny_text, self._tick)
            self.denyButton.setText(self._timeout_text)

    @QtCore.pyqtSlot()
    def on_timeout_triggered(self):
        self._timeout_triggered = True
        self._send_rule()

    def _configure_default_duration(self):
        if self._cfg.getSettings(
                "global/default_duration") == self.DURATION_once:
            self.durationCombo.setCurrentIndex(0)
        elif self._cfg.getSettings(
                "global/default_duration") == self.DURATION_30s:
            self.durationCombo.setCurrentIndex(1)
        elif self._cfg.getSettings(
                "global/default_duration") == self.DURATION_5m:
            self.durationCombo.setCurrentIndex(2)
        elif self._cfg.getSettings(
                "global/default_duration") == self.DURATION_15m:
            self.durationCombo.setCurrentIndex(3)
        elif self._cfg.getSettings(
                "global/default_duration") == self.DURATION_30m:
            self.durationCombo.setCurrentIndex(4)
        elif self._cfg.getSettings(
                "global/default_duration") == self.DURATION_1h:
            self.durationCombo.setCurrentIndex(5)
        elif self._cfg.getSettings(
                "global/default_duration") == self.DURATION_session:
            self.durationCombo.setCurrentIndex(6)
        elif self._cfg.getSettings(
                "global/default_duration") == self.DURATION_forever:
            self.durationCombo.setCurrentIndex(7)
        else:
            # default to "for this session"
            self.durationCombo.setCurrentIndex(6)

    def _set_cmd_action_text(self):
        if self._cfg.getSettings(self.CFG_DEFAULT_ACTION) == self.ACTION_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)
        self.checkAdvanced.setFocus()

    def _render_connection(self, con):
        app_name, app_icon, _ = self._apps_parser.get_info_by_path(
            con.process_path, "terminal")
        if app_name != 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)
            self._set_elide_text(self.appPathLabel, "(%s)" % con.process_path)
        else:
            self.appPathLabel.setFixedHeight(1)
            self.appPathLabel.setText("")

        if app_name == "":
            app_name = "Unknown process"
            self.appNameLabel.setText("Outgoing connection")
        else:
            self.appNameLabel.setText(app_name)
            self.appNameLabel.setToolTip(app_name)

        self.cwdLabel.setToolTip("Process launched from: %s" % con.process_cwd)
        self._set_elide_text(self.cwdLabel, con.process_cwd, max_size=32)

        icon = QtGui.QIcon().fromTheme(app_icon)
        pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
        self.iconLabel.setPixmap(pixmap)

        if self._local:
            message = "<b>%s</b> is connecting to <b>%s</b> on %s port %d" % ( \
                        app_name,
                        con.dst_host or con.dst_ip,
                        con.protocol,
                        con.dst_port )
        else:
            message = "<b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d" % ( \
                        app_name,
                        self._peer.split(':')[1],
                        con.dst_host or con.dst_ip,
                        con.protocol,
                        con.dst_port )

        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._set_elide_text(self.argsLabel, ' '.join(con.process_args))
        self.argsLabel.setToolTip(' '.join(con.process_args))

        self.whatCombo.clear()
        self.whatIPCombo.clear()
        if int(con.process_id) > 0:
            self.whatCombo.addItem("from this executable",
                                   self.FIELD_PROC_PATH)

        self.whatCombo.addItem("from this command line", self.FIELD_PROC_ARGS)
        if self.argsLabel.text() == "":
            self._set_elide_text(self.argsLabel, con.process_path)

        # the order of the entries must match those in the preferences dialog
        # prefs -> UI -> Default target
        self.whatCombo.addItem("to port %d" % con.dst_port,
                               self.FIELD_DST_PORT)
        self.whatCombo.addItem("to %s" % con.dst_ip, self.FIELD_DST_IP)
        if int(con.user_id) >= 0:
            self.whatCombo.addItem("from user %s" % uid, self.FIELD_USER_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("to %s" % con.dst_ip, self.FIELD_DST_IP)

        parts = con.dst_ip.split('.')
        nparts = len(parts)
        for i in range(1, nparts):
            self.whatCombo.addItem("to %s.*" % '.'.join(parts[:i]),
                                   self.FIELD_REGEX_IP)
            self.whatIPCombo.addItem("to %s.*" % '.'.join(parts[:i]),
                                     self.FIELD_REGEX_IP)

        self._add_dst_networks_to_combo(self.whatIPCombo, con.dst_ip)

        self._default_action = self._cfg.getSettings(self.CFG_DEFAULT_ACTION)

        self._configure_default_duration()

        if int(con.process_id) > 0:
            self.whatCombo.setCurrentIndex(
                int(self._cfg.getSettings("global/default_target")))
        else:
            self.whatCombo.setCurrentIndex(2)

        self._set_cmd_action_text()

        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(
                "to %s" % ipaddress.ip_network(dst_ip + "/24", strict=False),
                self.FIELD_DST_NETWORK)
            combo.addItem(
                "to %s" % ipaddress.ip_network(dst_ip + "/16", strict=False),
                self.FIELD_DST_NETWORK)
            combo.addItem(
                "to %s" % ipaddress.ip_network(dst_ip + "/8", strict=False),
                self.FIELD_DST_NETWORK)
        else:
            combo.addItem(
                "to %s" % ipaddress.ip_network(dst_ip + "/64", strict=False),
                self.FIELD_DST_NETWORK)
            combo.addItem(
                "to %s" % 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("to *.%s" % '.'.join(parts[i:]),
                                   self.FIELD_REGEX_HOST)
            self.whatIPCombo.addItem("to *.%s" % '.'.join(parts[i:]),
                                     self.FIELD_REGEX_HOST)

        if nparts == 1:
            self.whatCombo.addItem("to *%s" % dst_host, self.FIELD_REGEX_HOST)
            self.whatIPCombo.addItem("to *%s" % dst_host,
                                     self.FIELD_REGEX_HOST)

    def _get_duration(self, duration_idx):
        if duration_idx == 0:
            return self.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 self.DURATION_restart
        else:
            return self.DURATION_always

    def _get_combo_operator(self, combo, what_idx):
        if combo.itemData(what_idx) == self.FIELD_PROC_PATH:
            return "simple", "process.path", self._con.process_path

        elif combo.itemData(what_idx) == self.FIELD_PROC_ARGS:
            return "simple", "process.command", ' '.join(
                self._con.process_args)

        elif combo.itemData(what_idx) == self.FIELD_USER_ID:
            return "simple", "user.id", "%s" % self._con.user_id

        elif combo.itemData(what_idx) == self.FIELD_DST_PORT:
            return "simple", "dest.port", "%s" % self._con.dst_port

        elif combo.itemData(what_idx) == self.FIELD_DST_IP:
            return "simple", "dest.ip", self._con.dst_ip

        elif combo.itemData(what_idx) == self.FIELD_DST_HOST:
            return "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"
            return "network", "dest.network", combo.currentText()[3:]

        elif combo.itemData(what_idx) == self.FIELD_REGEX_HOST:
            return "regexp", "dest.host", "%s" % '\.'.join(
                combo.currentText().split('.')).replace("*", ".*")[3:]

        elif combo.itemData(what_idx) == self.FIELD_REGEX_IP:
            return "regexp", "dest.ip", "%s" % '\.'.join(
                combo.currentText().split('.')).replace("*", ".*")[3:]

    def _on_deny_clicked(self):
        self._default_action = self.ACTION_DENY
        self._send_rule()

    def _on_apply_clicked(self):
        self._default_action = self.ACTION_ALLOW
        self._send_rule()

    def _get_rule_name(self, rule):
        rule_temp_name = slugify("%s %s" % (rule.action, rule.duration))
        if self._ischeckAdvanceded:
            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):
        self._cfg.setSettings("promptDialog/geometry", self.saveGeometry())
        self._rule = ui_pb2.Rule(name="user.choice")
        self._rule.enabled = True
        self._rule.action = self._default_action
        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
            self._done.set()
            return

        rule_temp_name = self._get_rule_name(self._rule)
        self._rule.name = rule_temp_name

        # TODO: move to a method
        data = []
        if self._ischeckAdvanceded and 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._ischeckAdvanceded and self.checkDstPort.isChecked(
        ) and self.whatCombo.itemData(what_idx) != self.FIELD_DST_PORT:
            data.append({
                "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._ischeckAdvanceded and self.checkUserID.isChecked(
        ) and self.whatCombo.itemData(what_idx) != self.FIELD_USER_ID:
            data.append({
                "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._ischeckAdvanceded:
            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 = "list"
            self._rule.operator.operand = ""

        self._rule.name = rule_temp_name

        self.hide()
        if self._ischeckAdvanceded:
            self.checkAdvanced.toggle()
        self._ischeckAdvanceded = False

        # signal that the user took a decision and
        # a new rule is available
        self._done.set()
Ejemplo n.º 7
0
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_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):
        QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint)
        # Other interesting flags: QtCore.Qt.Tool | QtCore.Qt.BypassWindowManagerHint
        self._cfg = Config.get()
        self.setupUi(self)

        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._checkbox_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 showEvent(self, event):
        super(PromptDialog, self).showEvent(event)
        self.resize(540, 300)
        self.activateWindow()

    def _checkbox_toggled(self, state):
        self.applyButton.setText("%s" % self._apply_text)
        self.denyButton.setText("%s" % self._deny_text)
        self._tick_thread.stop = 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 _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:
            self.appDescriptionLabel.setVisible(True)
            self.appDescriptionLabel.setToolTip(description)
            self._set_elide_text(self.appDescriptionLabel, "%s" % description)
        else:
            self.appDescriptionLabel.setVisible(False)
            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.appNameLabel.setText(app_name)
            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()
        if int(con.process_id) > 0:
            self.whatCombo.addItem(QC.translate("popups", "from this executable"), self.FIELD_PROC_PATH)

        self.whatCombo.addItem(QC.translate("popups", "from this command line"), self.FIELD_PROC_ARGS)

        # the order of the entries must match those in the preferences dialog
        # prefs -> UI -> Default target
        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)
        if int(con.user_id) >= 0:
            self.whatCombo.addItem(QC.translate("popups", "from user {0}").format(uid), self.FIELD_USER_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._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)

        if nparts == 1:
            self.whatCombo.addItem(QC.translate("popups", "to *{0}").format(dst_host), self.FIELD_REGEX_HOST)
            self.whatIPCombo.addItem(QC.translate("popups", "to *{0}").format(dst_host), 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.
        """
        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)))

        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 "simple", "process.path", self._con.process_path

        elif combo.itemData(what_idx) == self.FIELD_PROC_ARGS:
            return "simple", "process.command", ' '.join(self._con.process_args)

        elif combo.itemData(what_idx) == self.FIELD_USER_ID:
            return "simple", "user.id", "%s" % self._con.user_id

        elif combo.itemData(what_idx) == self.FIELD_DST_PORT:
            return "simple", "dest.port", "%s" % self._con.dst_port

        elif combo.itemData(what_idx) == self.FIELD_DST_IP:
            return "simple", "dest.ip", self._con.dst_ip

        elif combo.itemData(what_idx) == self.FIELD_DST_HOST:
            return "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 "network", "dest.network", text

        elif combo.itemData(what_idx) == self.FIELD_REGEX_HOST:
            parts = combo.currentText().split(' ')
            text = parts[len(parts)-1]
            return "regexp", "dest.host", "%s" % '\.'.join(text.split('.')).replace("*", ".*")

        elif combo.itemData(what_idx) == self.FIELD_REGEX_IP:
            parts = combo.currentText().split(' ')
            text = parts[len(parts)-1]
            return "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 _get_rule_name(self, rule):
        rule_temp_name = slugify("%s %s" % (rule.action, rule.duration))
        if self._ischeckAdvanceded:
            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):
        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
            self._done.set()
            return

        rule_temp_name = self._get_rule_name(self._rule)
        self._rule.name = rule_temp_name

        # TODO: move to a method
        data=[]
        if self._ischeckAdvanceded and 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._ischeckAdvanceded and self.checkDstPort.isChecked() and self.whatCombo.itemData(what_idx) != self.FIELD_DST_PORT:
            data.append({"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._ischeckAdvanceded and self.checkUserID.isChecked() and self.whatCombo.itemData(what_idx) != self.FIELD_USER_ID:
            data.append({"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._ischeckAdvanceded:
            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 = "list"
            self._rule.operator.operand = ""

        self._rule.name = rule_temp_name

        self.hide()
        if self._ischeckAdvanceded:
            self.checkAdvanced.toggle()
        self._ischeckAdvanceded = False

        # signal that the user took a decision and
        # a new rule is available
        self._done.set()
Ejemplo n.º 8
0
class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
    _trigger = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        QtWidgets.QDialog.__init__(self, parent,
                                   QtCore.Qt.WindowStaysOnTopHint)

        self.setupUi(self)

        self.setWindowTitle("opensnitch-qt v%s" % version)

        self._lock = threading.Lock()
        self._con = None
        self._rule = None
        self._trigger.connect(self.on_connection_triggered)
        self._done = threading.Event()

        self._apps_parser = LinuxDesktopParser()

        self._app_name_label = self.findChild(QtWidgets.QLabel, "appNameLabel")
        self._app_icon_label = self.findChild(QtWidgets.QLabel, "iconLabel")
        self._message_label = self.findChild(QtWidgets.QLabel, "messageLabel")

        self._src_ip_label = self.findChild(QtWidgets.QLabel, "sourceIPLabel")
        self._dst_ip_label = self.findChild(QtWidgets.QLabel, "destIPLabel")
        self._dst_port_label = self.findChild(QtWidgets.QLabel,
                                              "destPortLabel")
        self._dst_host_label = self.findChild(QtWidgets.QLabel,
                                              "destHostLabel")
        self._uid_label = self.findChild(QtWidgets.QLabel, "uidLabel")
        self._pid_label = self.findChild(QtWidgets.QLabel, "pidLabel")
        self._args_label = self.findChild(QtWidgets.QLabel, "argsLabel")

        self._apply_button = self.findChild(QtWidgets.QPushButton,
                                            "applyButton")
        self._apply_button.clicked.connect(self._on_apply_clicked)

        self._action_combo = self.findChild(QtWidgets.QComboBox, "actionCombo")
        self._what_combo = self.findChild(QtWidgets.QComboBox, "whatCombo")
        self._duration_combo = self.findChild(QtWidgets.QComboBox,
                                              "durationCombo")

    def promptUser(self, connection):
        # one at a time
        with self._lock:
            # reset state
            self._rule = None
            self._con = connection
            self._done.clear()
            # trigger on_connection_triggered
            self._trigger.emit()
            # wait for user choice
            self._done.wait()

            return self._rule

    @QtCore.pyqtSlot()
    def on_connection_triggered(self):
        self._render_connection(self._con)
        self.show()

    def _render_connection(self, con):
        app_name, app_icon, desk = self._apps_parser.get_info_by_path(
            con.process_path, "terminal")
        if app_name == "":
            self._app_name_label.setText(con.process_path)
        else:
            self._app_name_label.setText(app_name)

        icon = QtGui.QIcon().fromTheme(app_icon)
        pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
        self._app_icon_label.setPixmap(pixmap)

        self._message_label.setText("<b>%s</b> is connecting to <b>%s</b> on %s port %d" % ( \
            con.process_path,
            con.dst_host or con.dst_ip,
            con.protocol,
            con.dst_port
        ))

        self._src_ip_label.setText(con.src_ip)
        self._dst_ip_label.setText(con.dst_ip)
        self._dst_port_label.setText("%s" % con.dst_port)
        self._dst_host_label.setText(con.dst_host)
        self._uid_label.setText(
            "%d (%s)" % (con.user_id, pwd.getpwuid(con.user_id).pw_name))
        self._pid_label.setText("%s" % con.process_id)
        self._args_label.setText(' '.join(con.process_args))

        self._what_combo.clear()
        self._what_combo.addItem("from this process")
        self._what_combo.addItem("from user %d" % con.user_id)
        self._what_combo.addItem("to port %d" % con.dst_port)
        self._what_combo.addItem("to %s" % con.dst_ip)
        if con.dst_host != "":
            self._what_combo.addItem("to %s" % con.dst_host)

        self._what_combo.setCurrentIndex(0)
        self._action_combo.setCurrentIndex(0)
        self._duration_combo.setCurrentIndex(1)

        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(Dialog, self).keyPressEvent(event)

    # prevent a click on the window's x
    # from quitting the whole application
    def closeEvent(self, e):
        self._on_apply_clicked()
        e.ignore()

    def _on_apply_clicked(self):
        self._rule = ui_pb2.RuleReply(name="user.choice")

        action_idx = self._action_combo.currentIndex()
        if action_idx == 0:
            self._rule.action = "allow"
        else:
            self._rule.action = "deny"

        duration_idx = self._duration_combo.currentIndex()
        if duration_idx == 0:
            self._rule.duration = "once"
        elif duration_idx == 1:
            self._rule.duration = "until restart"
        else:
            self._rule.duration = "always"

        what_idx = self._what_combo.currentIndex()
        if what_idx == 0:
            self._rule.what = "process.path"
            self._rule.value = self._con.process_path

        elif what_idx == 1:
            self._rule.what = "user.id"
            self._rule.value = "%s" % self._con.user_id

        elif what_idx == 2:
            self._rule.what = "dest.port"
            self._rule.value = "%s" % self._con.dst_port

        elif what_idx == 3:
            self._rule.what = "dest.ip"
            self._rule.value = self._con.dst_ip

        else:
            self._rule.what = "dest.host"
            self._rule.value = self._con.dst_host

        self._rule.name = slugify(
            "%s %s %s" %
            (self._rule.action, self._rule.what, self._rule.value))

        self.hide()
        # signal that the user took a decision and
        # a new rule is available
        self._done.set()
Ejemplo n.º 9
0
class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
    _prompt_trigger = QtCore.pyqtSignal()
    _tick_trigger = QtCore.pyqtSignal()
    _timeout_trigger = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint)

        self._cfg = Config.get()
        self._db = Database.instance()

        dialog_geometry = self._cfg.getSettings("promptDialog/geometry")
        if dialog_geometry != None:
            self.restoreGeometry(dialog_geometry)

        self.setupUi(self)

        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 = self._cfg.default_timeout
        self._tick_thread = None
        self._done = threading.Event()
        self._apply_text = "Apply"

        self._apps_parser = LinuxDesktopParser()

        self._app_name_label = self.findChild(QtWidgets.QLabel, "appNameLabel")
        self._app_icon_label = self.findChild(QtWidgets.QLabel, "iconLabel")
        self._message_label = self.findChild(QtWidgets.QLabel, "messageLabel")

        self._src_ip_label = self.findChild(QtWidgets.QLabel, "sourceIPLabel")
        self._dst_ip_label = self.findChild(QtWidgets.QLabel, "destIPLabel")
        self._dst_port_label = self.findChild(QtWidgets.QLabel, "destPortLabel")
        self._uid_label = self.findChild(QtWidgets.QLabel, "uidLabel")
        self._pid_label = self.findChild(QtWidgets.QLabel, "pidLabel")
        self._args_label = self.findChild(QtWidgets.QLabel, "argsLabel")

        self._apply_button = self.findChild(QtWidgets.QPushButton, "applyButton")
        self._apply_button.clicked.connect(self._on_apply_clicked)

        self._action_combo = self.findChild(QtWidgets.QComboBox, "actionCombo")
        self._what_combo = self.findChild(QtWidgets.QComboBox, "whatCombo")
        self._what_dstip_combo = self.findChild(QtWidgets.QComboBox, "whatIPCombo")
        self._duration_combo = self.findChild(QtWidgets.QComboBox, "durationCombo")
        self._what_dstip_combo.setVisible(False)

        self._dst_ip_check = self.findChild(QtWidgets.QCheckBox, "checkDstIP")
        self._dst_port_check = self.findChild(QtWidgets.QCheckBox, "checkDstPort")
        self._uid_check = self.findChild(QtWidgets.QCheckBox, "checkUserID")
        self._advanced_check = self.findChild(QtWidgets.QPushButton, "checkAdvanced")
        self._dst_ip_check.setVisible(False)
        self._dst_port_check.setVisible(False)
        self._uid_check.setVisible(False)

        self._is_advanced_checked = False
        self._advanced_check.toggled.connect(self._checkbox_toggled)

    def _checkbox_toggled(self, state):
        self._apply_button.setText("%s" % self._apply_text)
        self._tick_thread.stop = state

        self._dst_ip_check.setVisible(state)
        self._what_dstip_combo.setVisible(state)
        self._dst_ip_label.setVisible(not state)
        self._dst_port_check.setVisible(state)
        self._uid_check.setVisible(state)
        self._is_advanced_checked = state

    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._tick = self._cfg.default_timeout
            self._tick_thread = threading.Thread(target=self._timeout_worker)
            self._tick_thread.stop = self._is_advanced_checked
            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

    def _timeout_worker(self):
        while self._tick > 0 and self._done.is_set() is False:
            t = threading.currentThread()
            if getattr(t, "stop", True):
                self._tick = self._cfg.default_timeout
                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)
        self.show()

    @QtCore.pyqtSlot()
    def on_tick_triggered(self):
        self._apply_button.setText("%s (%d)" % (self._apply_text, self._tick))

    @QtCore.pyqtSlot()
    def on_timeout_triggered(self):
        self._on_apply_clicked()

    def _render_connection(self, con):
        if self._local:
            app_name, app_icon, _ = self._apps_parser.get_info_by_path(con.process_path, "terminal")
        else:
            app_name, app_icon = "", "terminal"

        if app_name == "":
            app_name = "Unknown process"
            self._app_name_label.setText("Outgoing connection")
        else:
            self._app_name_label.setText(app_name)

        icon = QtGui.QIcon().fromTheme(app_icon)
        pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
        self._app_icon_label.setPixmap(pixmap)

        if self._local:
            message = "<b>%s</b> is connecting to <b>%s</b> on %s port %d" % ( \
                        app_name,
                        con.dst_host or con.dst_ip,
                        con.protocol,
                        con.dst_port )
        else:
            message = "The process <b>%s</b> running on the computer <b>%s</b> is connecting to <b>%s</b> on %s port %d" % ( \
                        app_name,
                        self._peer.split(':')[1],
                        con.dst_host or con.dst_ip,
                        con.protocol,
                        con.dst_port )

        self._message_label.setText(message)

        self._src_ip_label.setText(con.src_ip)
        self._dst_ip_label.setText(con.dst_ip)
        self._dst_port_label.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._uid_label.setText(uid)
        self._pid_label.setText("%s" % con.process_id)
        self._args_label.setText(' '.join(con.process_args))

        self._what_combo.clear()
        self._what_dstip_combo.clear()
        if int(con.process_id) > 0:
            self._what_combo.addItem("from this process", "process_id")
        if int(con.user_id) >= 0:
            self._what_combo.addItem("from user %s" % uid, "user_id")
        self._what_combo.addItem("to port %d" % con.dst_port, "dst_port")
        self._what_combo.addItem("to %s" % con.dst_ip, "dst_ip")

        if con.dst_host != "" and con.dst_host != con.dst_ip:
            self._what_combo.addItem("to %s" % con.dst_host, "simple_host")
            self._what_dstip_combo.addItem("to %s" % con.dst_host, "simple_host")

            parts = con.dst_host.split('.')[1:]
            nparts = len(parts)
            for i in range(0, nparts - 1):
                self._what_combo.addItem("to *.%s" % '.'.join(parts[i:]), "regex_host")
                self._what_dstip_combo.addItem("to *.%s" % '.'.join(parts[i:]), "regex_host")

        self._what_dstip_combo.addItem("to %s" % con.dst_ip, "dst_ip")

        parts = con.dst_ip.split('.')
        nparts = len(parts)
        for i in range(1, nparts):
            self._what_combo.addItem("to %s.*" % '.'.join(parts[:i]), "regex_ip")
            self._what_dstip_combo.addItem("to %s.*" % '.'.join(parts[:i]), "regex_ip")

        if self._cfg.default_action == "allow":
            self._action_combo.setCurrentIndex(0)
        else:
            self._action_combo.setCurrentIndex(1)

        if self._cfg.default_duration == "once":
            self._duration_combo.setCurrentIndex(0)
        elif self._cfg.default_duration == "30s":
            self._duration_combo.setCurrentIndex(1)
        elif self._cfg.default_duration == "5m":
            self._duration_combo.setCurrentIndex(2)
        elif self._cfg.default_duration == "15m":
            self._duration_combo.setCurrentIndex(3)
        elif self._cfg.default_duration == "30m":
            self._duration_combo.setCurrentIndex(4)
        elif self._cfg.default_duration == "1h":
            self._duration_combo.setCurrentIndex(5)
        elif self._cfg.default_duration == "until restart":
            self._duration_combo.setCurrentIndex(6)
        else:
            self._duration_combo.setCurrentIndex(7)

        if int(con.process_id) > 0:
            self._what_combo.setCurrentIndex(0)
        else:
            self._what_combo.setCurrentIndex(1)

        self._apply_button.setText("Apply (%d)" % self._tick)

        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._on_apply_clicked()
        e.ignore()

    def _get_duration(self, duration_idx):
        if duration_idx == 0:
            return "once"
        elif duration_idx == 1:
            return "30s"
        elif duration_idx == 2:
            return "5m"
        elif duration_idx == 3:
            return "15m"
        elif duration_idx == 4:
            return "30m"
        elif duration_idx == 5:
            return "1h"
        elif duration_idx == 6:
            return "until restart"
        else:
            return "always"

    def _get_combo_operator(self, combo, what_idx):
        if combo.itemData(what_idx) == "process_id":
            return "simple", "process.path", self._con.process_path

        elif combo.itemData(what_idx) == "user_id":
            return "simple", "user.id", "%s" % self._con.user_id
        
        elif combo.itemData(what_idx) == "dst_port":
            return "simple", "dest.port", "%s" % self._con.dst_port

        elif combo.itemData(what_idx) == "dst_ip":
            return "simple", "dest.ip", self._con.dst_ip
        
        elif combo.itemData(what_idx) == "simple_host":
            return "simple", "dest.host", self._con.dst_host

        elif combo.itemData(what_idx) == "regex_host":
            return "regexp", "dest.host", "%s" % '\.'.join(combo.currentText().split('.')).replace("*", ".*")[3:]

        elif combo.itemData(what_idx) == "regex_ip":
            return "regexp", "dest.ip", "%s" % '\.'.join(combo.currentText().split('.')).replace("*", ".*")[3:]

    def _on_apply_clicked(self):
        self._cfg.setSettings("promptDialog/geometry", self.saveGeometry())
        self._rule = ui_pb2.Rule(name="user.choice")

        action_idx = self._action_combo.currentIndex()
        if action_idx == 0:
            self._rule.action = "allow"
        else:
            self._rule.action = "deny"

        self._rule.duration = self._get_duration(self._duration_combo.currentIndex())

        what_idx = self._what_combo.currentIndex()
        self._rule.operator.type, self._rule.operator.operand, self._rule.operator.data = self._get_combo_operator(self._what_combo, what_idx)

        # TODO: move to a method
        is_advanced=False
        data=[]
        if self._dst_ip_check.isChecked() and (self._what_combo.itemData(what_idx) == "process_id" or
                self._what_combo.itemData(what_idx) == "user_id" or
                self._what_combo.itemData(what_idx) == "dst_port"):
            is_advanced=True
            _type, _operand, _data = self._get_combo_operator(self._what_dstip_combo, self._what_dstip_combo.currentIndex())
            data.append({"type": _type, "operand": _operand, "data": _data})

        if self._dst_port_check.isChecked() and self._what_combo.itemData(what_idx) != "dst_port":
            is_advanced=True
            data.append({"type": "simple", "operand": "dest.port", "data": str(self._con.dst_port)})

        if self._uid_check.isChecked() and self._what_combo.itemData(what_idx) != "user_id":
            is_advanced=True
            data.append({"type": "simple", "operand": "user.id", "data": str(self._con.user_id)})

        if is_advanced and self._advanced_check.isChecked():
            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 = "list"
            self._rule.operator.operand = ""

        self._rule.name = slugify("%s %s %s" % (self._rule.action, self._rule.operator.type, self._rule.operator.data))
        self.hide()
        if self._is_advanced_checked:
            self._advanced_check.toggle()
        self._id_advanced_checked = False

        # signal that the user took a decision and 
        # a new rule is available
        self._done.set()