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
|
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)
self.skip_download = opts.no_provider_checks
self.skip_verify = opts.no_ca_verify
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
|