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