summaryrefslogtreecommitdiff
path: root/src/leap/baseapp/eip.py
blob: b34cc82eef898087be28908b09f018e1fe08fcdb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
from __future__ import print_function
import logging
import time
#import sys

from PyQt4 import QtCore

from leap.baseapp.dialogs import ErrorDialog
from leap.baseapp import constants
from leap.eip import exceptions as eip_exceptions
from leap.eip.eipconnection import EIPConnection
from leap.base.checks import EVENT_CONNECT_REFUSED
from leap.util import geo

logger = logging.getLogger(name=__name__)


class EIPConductorAppMixin(object):
    """
    initializes an instance of EIPConnection,
    gathers errors, and passes status-change signals
    from Qt land along to the conductor.
    Connects the eip connect/disconnect logic
    to the switches in the app (buttons/menu items).
    """
    ERR_DIALOG = False

    def __init__(self, *args, **kwargs):
        opts = kwargs.pop('opts')
        config_file = getattr(opts, 'config_file', None)
        provider = kwargs.pop('provider')

        self.eip_service_started = False

        # conductor (eip connection) is in charge of all
        # vpn-related configuration / monitoring.
        # we pass a tuple of signals that will be
        # triggered when status changes.

        self.conductor = EIPConnection(
            watcher_cb=self.newLogLine.emit,
            config_file=config_file,
            checker_signals=(self.eipStatusChange.emit, ),
            status_signals=(self.openvpnStatusChange.emit, ),
            debug=self.debugmode,
            ovpn_verbosity=opts.openvpn_verb,
            provider=provider)

        # Do we want to enable the skip checks w/o being
        # in debug mode??
        #self.skip_download = opts.no_provider_checks
        #self.skip_verify = opts.no_ca_verify
        self.skip_download = False
        self.skip_verify = False

    def run_eip_checks(self):
        """
        runs eip checks and
        the error checking loop
        """
        logger.debug('running EIP CHECKS')
        self.conductor.run_checks(
            skip_download=self.skip_download,
            skip_verify=self.skip_verify)
        self.error_check()

        self.start_eipconnection.emit()

    def error_check(self):
        """
        consumes the conductor error queue.
        pops errors, and acts accordingly (launching user dialogs).
        """
        logger.debug('error check')

        errq = self.conductor.error_queue
        while errq.qsize() != 0:
            logger.debug('%s errors left in conductor queue', errq.qsize())
            # we get exception and original traceback from queue
            error, tb = errq.get()

            # redundant log, debugging the loop.
            logger.error('%s: %s', error.__class__.__name__, error.message)

            if issubclass(error.__class__, eip_exceptions.EIPClientError):
                self.triggerEIPError.emit(error)

            else:
                # deprecated form of raising exception.
                raise error, None, tb

            if error.failfirst is True:
                break

    @QtCore.pyqtSlot(object)
    def onEIPError(self, error):
        """
        check severity and launches
        dialogs informing user about the errors.
        in the future we plan to derive errors to
        our log viewer.
        """
        if self.ERR_DIALOG:
            logger.warning('another error dialog suppressed')
            return

        # XXX this is actually a one-shot.
        # On the dialog there should be
        # a reset signal binded to the ok button
        # or something like that.
        self.ERR_DIALOG = True

        if getattr(error, 'usermessage', None):
            message = error.usermessage
        else:
            message = error.message

        # XXX
        # check headless = False before
        # launching dialog.
        # (so Qt tests can assert stuff)

        if error.critical:
            logger.critical(error.message)
            #critical error (non recoverable),
            #we give user some info and quit.
            #(critical error dialog will exit app)
            ErrorDialog(errtype="critical",
                        msg=message,
                        label="critical error")

        elif error.warning:
            logger.warning(error.message)

        else:
            dialog = ErrorDialog()
            dialog.warningMessage(message, 'error')

    @QtCore.pyqtSlot()
    def statusUpdate(self):
        """
        polls status and updates ui with real time
        info about transferred bytes / connection state.
        right now is triggered by a timer tick
        (timer controlled by StatusAwareTrayIcon class)
        """
        # TODO I guess it's too expensive to poll
        # continously. move to signal events instead.
        # (i.e., subscribe to connection status changes
        # from openvpn manager)

        if not self.eip_service_started:
            # there is a race condition
            # going on here. Depending on how long we take
            # to init the qt app, the management socket
            # is not ready yet.
            return

        #if self.conductor.with_errors:
            #XXX how to wait on pkexec???
            #something better that this workaround, plz!!
            #I removed the pkexec pass authentication at all.
            #time.sleep(5)
            #logger.debug('timeout')
            #logger.error('errors. disconnect')
            #self.start_or_stopVPN()  # is stop

        state = self.conductor.poll_connection_state()
        if not state:
            return

        ts, con_status, ok, ip, remote = state
        self.set_statusbarMessage(con_status)
        self.setIconToolTip()

        ts = time.strftime("%a %b %d %X", ts)
        if self.debugmode:
            self.updateTS.setText(ts)
            self.status_label.setText(con_status)
            self.ip_label.setText(ip)
            self.remote_label.setText(remote)
            self.remote_country.setText(
                geo.get_country_name(remote))

        # status i/o

        status = self.conductor.get_status_io()
        if status and self.debugmode:
            #XXX move this to systray menu indicators
            ts, (tun_read, tun_write, tcp_read, tcp_write, auth_read) = status
            ts = time.strftime("%a %b %d %X", ts)
            self.updateTS.setText(ts)
            self.tun_read_bytes.setText(tun_read)
            self.tun_write_bytes.setText(tun_write)

        # connection information via management interface
        log = self.conductor.get_log()
        error_matrix = [(EVENT_CONNECT_REFUSED, (self.start_or_stopVPN, ))]
        if hasattr(self.network_checker, 'checker'):
            self.network_checker.checker.parse_log_and_react(log, error_matrix)

    @QtCore.pyqtSlot()
    def start_or_stopVPN(self, **kwargs):
        """
        stub for running child process with vpn
        """
        if self.conductor.has_errors():
            logger.debug('not starting vpn; conductor has errors')
            return

        if self.eip_service_started is False:
            try:
                self.conductor.connect()

            except eip_exceptions.EIPNoCommandError as exc:
                logger.error('tried to run openvpn but no command is set')
                self.triggerEIPError.emit(exc)

            except Exception as err:
                # raise generic exception (Bad Thing Happened?)
                logger.exception(err)
            else:
                # no errors, so go on.
                if self.debugmode:
                    self.startStopButton.setText(self.tr('&Disconnect'))
                self.eip_service_started = True
                self.toggleEIPAct()

                # XXX decouple! (timer is init by icons class).
                # we could bring Timer Init to this Mixin
                # or to its own Mixin.
                self.timer.start(constants.TIMER_MILLISECONDS)
            return

        if self.eip_service_started is True:
            self.network_checker.stop()
            self.conductor.disconnect()
            if self.debugmode:
                self.startStopButton.setText(self.tr('&Connect'))
            self.eip_service_started = False
            self.toggleEIPAct()
            self.timer.stop()
            return