コード例 #1
0
ファイル: prompt.py プロジェクト: security-geeks/opensnitch
    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")))
コード例 #2
0
ファイル: processdetails.py プロジェクト: tioguda/opensnitch
    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"))
コード例 #3
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)
コード例 #4
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_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()
コード例 #5
0
ファイル: prompt.py プロジェクト: evilsocket/opensnitch
    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)